Back to Impact Yard
SOURCE / PINNED RELEASE

Made of little things.

Impact Yard

Release
09fe6aebec3c
Author-recorded commit
0696a4211733…
License
LICENSE
Author’s source reference
nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-0b3d7b60562

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

tests/browser-smoke.mjs
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(), '7');
  assert.equal(await app.locator('.structure').count(), 7);
  assert.match(await app.locator('#prop-total').innerText(), /157/);
  for (const name of ['Domino run', 'Timber tower', 'Smash arch']) assert.ok((await app.locator('#structure-list').innerText()).includes(name));
  report.checks.push({ structures: 7, movablePieces: 157 });
  const injected = await app.evaluate(() => Object.keys(window.napplet ?? {}));
  report.checks.push({ host: 'allow-scripts', injected });
  await app.locator('#camera').click(); await page.waitForTimeout(200);
  assert.equal(await app.locator('#camera-label').innerText(), 'Orbit camera');
  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('#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;
      const controls = [...document.querySelectorAll('button')].filter(el => el.getClientRects().length > 0).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);
  report.checks.push({ pointerDriving: 'accelerates and releases' });
  // Drive toward both new side obstacles to inspect their actual rendered meshes.
  await page.setViewportSize({ width: 1440, height: 1000 });
  await iframe.evaluate(el => { el.style.width = '1440px'; el.style.height = '860px'; });
  for (const [direction, name] of [['a', 'domino-run'], ['d', 'timber-tower']]) {
    await game.locator('#rebuild').click();
    await game.locator('#scene').click({ position: { x: 700, y: 400 } });
    await page.keyboard.down('w'); await page.keyboard.down(direction);
    await page.waitForTimeout(1000); await page.keyboard.up(direction);
    await page.waitForTimeout(400); await page.keyboard.up('w');
    await game.locator('#pause').click();
    await game.locator('#camera').click(); await game.locator('#camera').click();
    await iframe.screenshot({ path: `${outDir}/${name}.png` });
    await game.locator('#camera').click(); await game.locator('#pause').click();
  }
  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(); }