SOURCE / PINNED RELEASE
Made of little things.
Impact Yard remix
- Release
- 5b31a37bbd07…
- Author-recorded commit
- 7762f7efb018…
- License
- LICENSE
- Author’s source reference
- nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-c1d8c061b02
Archive hash verified: c640627baa321925…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
const require = createRequire(import.meta.resolve('@napplet/conformance-cli/package.json'));
const { chromium } = require('playwright');
const outDir = '/tmp/impact-yard-checks';
await mkdir(outDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
const report = { errors: [], checks: [], frames: [] };
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, deviceScaleFactor: 1 });
page.on('pageerror', error => report.errors.push(error.message));
await page.goto(process.env.SIM_PREVIEW_URL || 'http://127.0.0.1:4184/');
const iframe = page.locator('iframe'); await iframe.waitFor();
const app = page.frames().find(frame => frame !== page.mainFrame());
await app.locator('#scene').waitFor();
await page.waitForTimeout(800);
assert.equal(await app.locator('#retry').isVisible(), false);
assert.equal(await app.locator('#structure-count').innerText(), '17');
assert.equal(await app.locator('.structure').count(), 17);
assert.match(await app.locator('#prop-total').innerText(), /590/);
for (const name of ['Skybridge twins', 'Freight avalanche', 'Cascade viaduct', 'Helix megatower', 'Cathedral cascade']) assert.ok((await app.locator('#structure-list').innerText()).includes(name));
report.checks.push({ structures: 17, movablePieces: 590 });
const injected = await app.evaluate(() => Object.keys(window.napplet ?? {}));
report.checks.push({ host: 'allow-scripts', injected });
for (let i = 0; i < 3 && await app.locator('#camera-label').innerText() !== 'Chase camera'; i++) await app.locator('#camera').click();
await app.locator('#camera').click(); await page.waitForTimeout(200);
assert.equal(await app.locator('#camera-label').innerText(), 'Orbit camera');
await iframe.screenshot({ path: `${outDir}/booster-geometry.png` });
await page.reload(); await page.waitForTimeout(1200);
const restored = page.frames().find(frame => frame !== page.mainFrame());
assert.equal(await restored.locator('#camera-label').innerText(), 'Orbit camera');
report.checks.push({ cameraPersistence: await restored.locator('#camera-label').innerText() });
// Subsequent checks use the reloaded runtime frame.
const game = restored;
await game.locator('#camera').click(); await game.locator('#camera').click();
await game.locator('#rebuild').click();
await game.locator('#scene').click({ position: { x: 700, y: 400 } });
await page.keyboard.down('f');
await page.waitForTimeout(200);
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'firing');
const rocketSpeed = Number(await game.locator('#speed').innerText());
assert.ok(rocketSpeed > 90, `rocket kick: ${rocketSpeed}`);
await iframe.screenshot({ path: `${outDir}/rocket-boost.png` });
await game.waitForFunction(() => document.querySelector('#boost').dataset.state === 'ready', null, { timeout: 20000 });
await page.waitForTimeout(200);
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'ready', 'held F does not refire');
await page.keyboard.up('f');
await game.locator('#rebuild').click();
await game.locator('#pause').click();
await game.locator('#scene').click({ position: { x: 500, y: 250 } });
await page.keyboard.press('f');
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'ready');
await game.locator('#resume').click();
await page.keyboard.press('f');
await page.waitForTimeout(100);
await page.keyboard.down('Space'); await page.waitForTimeout(200); await page.keyboard.up('Space');
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'charging', 'brake cuts thrust');
await game.locator('#rebuild').click();
await game.locator('#boost').click(); await page.waitForTimeout(100);
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'firing');
await game.locator('#camera').focus(); await page.waitForTimeout(150);
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'charging', 'blur cuts thrust');
report.checks.push({ rocketKickKmh: rocketSpeed, rocketControls: 'F, pointer, recharge, no auto-repeat, pause, brake and blur' });
await game.locator('#rebuild').click();
await game.locator('#scene').click({ position: { x: 700, y: 400 } });
await page.keyboard.down('w');
await page.waitForTimeout(1600);
const speed = Number(await game.locator('#speed').innerText()); assert.ok(speed > 5, `accelerating: ${speed}`);
await page.keyboard.up('w');
await page.keyboard.down('Space'); await page.waitForTimeout(1500); await page.keyboard.up('Space');
const braked = Number(await game.locator('#speed').innerText()); assert.ok(braked < speed, `braking: ${braked}`);
report.checks.push({ acceleration: speed, braking: braked });
await game.locator('#pause').click();
assert.equal(await game.locator('#paused-notice').isVisible(), true);
await game.locator('#resume').click();
assert.equal(await game.locator('#paused-notice').isVisible(), false);
await game.locator('#slow').click(); assert.equal(await game.locator('#slow').getAttribute('aria-pressed'), 'true');
await game.locator('#slow').click();
await game.locator('#rebuild').click();
await game.locator('#scene').click({ position: { x: 700, y: 400 } });
await page.keyboard.down('w'); await page.waitForTimeout(6000); await page.keyboard.up('w');
const displaced = Number(await game.locator('#displaced').innerText());
assert.ok(displaced > 0, `collision displaced ${displaced}`);
report.checks.push({ collisionDisplaced: displaced });
await iframe.screenshot({ path: `${outDir}/collision.png` });
await game.locator('#rebuild').click(); await page.waitForTimeout(600);
assert.equal(Number(await game.locator('#displaced').innerText()), 0);
await game.locator('#camera').click(); await game.locator('#camera').click();
report.checks.push({ renderFps: await game.evaluate(() => new Promise(resolve => {
let start = performance.now(), frames = 0;
function tick(now) { frames++; if (now - start > 1000) resolve(Math.round(frames * 1000 / (now - start))); else requestAnimationFrame(tick); }
requestAnimationFrame(tick);
})) });
// Keep the actual runtime alive while resizing its frame; state must survive.
for (const [themeName, colors] of [['dark', { background: '#182420', text: '#edf2e9', primary: '#e4ac6b' }], ['light', { background: '#f5f3e9', text: '#28352c', primary: '#367c66' }]]) {
// Exercise the installed runtime's documented theme push from the parent frame.
await iframe.evaluate((el, colors) => el.contentWindow.postMessage({ type: 'theme.changed', theme: { colors } }, '*'), colors);
await page.waitForTimeout(150);
assert.equal(await game.evaluate(() => document.documentElement.style.getPropertyValue('--bg')), colors.background);
for (const [width, height] of [[200, 160], [320, 560], [900, 600], [2400, 1200]]) {
await page.setViewportSize({ width: Math.max(width, 480), height: height + 140 });
await iframe.evaluate((el, size) => { el.style.width = `${size[0]}px`; el.style.height = `${size[1]}px`; el.style.minHeight = '0'; el.style.minWidth = '0'; el.style.flex = 'none'; }, [width, height]);
await page.waitForTimeout(250);
const layout = await game.evaluate(() => {
const root = document.documentElement;
// Structure rows live in a scrolling region; viewport controls stay fixed.
const controls = [...document.querySelectorAll('button')].filter(el => el.getClientRects().length > 0 && !el.closest('.yard-guide')).map(el => {
const r = el.getBoundingClientRect(); return { label: el.getAttribute('aria-label') || el.textContent, x: r.x, y: r.y, right: r.right, bottom: r.bottom };
});
return { w: innerWidth, h: innerHeight, scrollW: root.scrollWidth, scrollH: root.scrollHeight, controls, bg: getComputedStyle(root).backgroundColor };
});
assert.equal(layout.w, width); assert.equal(layout.h, height);
assert.ok(layout.scrollW <= width && layout.scrollH <= height, `overflow at ${width}×${height}`);
for (const control of layout.controls) assert.ok(control.x >= 0 && control.y >= 0 && control.right <= width + 1 && control.bottom <= height + 1, `clipped ${control.label} at ${width}×${height}`);
report.frames.push({ themeName, ...layout });
await iframe.screenshot({ path: `${outDir}/${themeName}-${width}x${height}.png` });
}
}
await page.setViewportSize({ width: 480, height: 700 });
await iframe.evaluate(el => { el.style.width = '320px'; el.style.height = '560px'; });
await game.locator('#rebuild').click();
const pedal = await game.getByRole('button', { name: 'Accelerate', exact: true }).boundingBox();
await page.mouse.move(pedal.x + pedal.width / 2, pedal.y + pedal.height / 2);
await page.mouse.down(); await page.waitForTimeout(1200); await page.mouse.up();
assert.ok(Number(await game.locator('#speed').innerText()) > 5);
assert.equal(await game.locator('.pressed').count(), 0);
await game.locator('#rebuild').click();
await game.locator('#boost').click(); await page.waitForTimeout(100);
assert.equal(await game.locator('#boost').getAttribute('data-state'), 'firing');
assert.ok(Number(await game.locator('#speed').innerText()) > 90);
report.checks.push({ pointerDriving: 'accelerates and releases' });
await game.locator('#rebuild').click();
await game.locator('#site').click();
assert.equal(await game.locator('[data-site="12"]').getAttribute('aria-pressed'), 'true');
await page.keyboard.press('n');
assert.equal(await game.locator('[data-site="13"]').getAttribute('aria-pressed'), 'true');
report.checks.push({ compactSiteControls: 'button and N visit the new complexes' });
// Inspect actual multi-storey collapses from the structure-centered orbit camera.
await page.setViewportSize({ width: 1440, height: 1000 });
await iframe.evaluate(el => { el.style.width = '1440px'; el.style.height = '860px'; });
for (const [site, name] of [[12, 'skybridge-twins'], [13, 'freight-avalanche'], [14, 'cascade-viaduct'], [15, 'helix-megatower'], [16, 'cathedral-cascade']]) {
await game.locator('#rebuild').click();
await game.locator(`[data-site="${site}"]`).click();
for (let i = 0; i < 3 && await game.locator('#camera-label').innerText() !== 'Orbit camera'; i++) await game.locator('#camera').click();
await iframe.screenshot({ path: `${outDir}/${name}-standing.png` });
await game.locator('#scene').focus();
await page.keyboard.press('f');
await page.waitForTimeout(2500);
await iframe.screenshot({ path: `${outDir}/${name}-falling.png` });
await page.keyboard.down('Space'); await page.waitForTimeout(8000); await page.keyboard.up('Space');
const pieces = Number(await game.locator('#displaced').innerText());
assert.ok(pieces >= 15, `${name} should collapse visibly (${pieces} displaced)`);
await iframe.screenshot({ path: `${outDir}/${name}-collapsed.png` });
report.checks.push({ collapseSite: name, displaced: pieces });
}
const bare = await browser.newPage({ viewport: { width: 900, height: 600 } });
bare.on('pageerror', error => report.errors.push(error.message));
await bare.setContent('<iframe sandbox="allow-scripts" style="width:900px;height:600px;border:0"></iframe>');
await bare.locator('iframe').evaluate((el, html) => { el.srcdoc = html; }, await readFile(new URL('../dist/index.html', import.meta.url), 'utf8'));
await bare.waitForTimeout(1200);
const offline = bare.frames().find(frame => frame !== bare.mainFrame());
assert.equal(await offline.locator('#retry').isVisible(), false);
await offline.locator('#camera').click();
assert.equal(await offline.locator('#camera-label').innerText(), 'Orbit camera');
report.checks.push({ optionalDomainsAbsent: 'game and camera work' });
assert.deepEqual(report.errors, []);
await writeFile(`${outDir}/report.json`, JSON.stringify(report, null, 2));
console.log(JSON.stringify({ errors: report.errors, checks: report.checks, frames: report.frames.map(f => `${f.themeName} ${f.w}×${f.h}`), report: `${outDir}/report.json` }, null, 2));
} finally { await browser.close(); }