SOURCE / PINNED RELEASE
Made of little things.
Powder Tool V600Billion
- Release
- 142767edcab8…
- Author-recorded commit
- 6d92971effd0…
- License
- LICENSE
- Author’s source reference
- nostr://npub1fllw8kw0thjj55wds0uugcnp5kej2nfxd36eruq39d56wwz8r44q5q78wj/wss%3A%2F%2Fgit.napplet.soy%2F/powder-toy
Archive hash verified: ed7d6a8ea7083197…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
/**
* Try out a scene while writing it: run a Lua scene file in the built napplet
* (dist/index.html, through scripts/harness.mjs) and take pictures of it.
*
* node scripts/scene-lab.mjs src/scenes/01-reactor.lua [--at 1,3,6] [--probe "<lua returning text>"]
*
* The file is read fresh each run, so the page need not be rebuilt for it.
* Pictures land in qa/lab-<name>-<seconds>s.png; after each one the particle
* count and the probe's answer are printed.
*/
import { readFileSync, mkdirSync } from 'node:fs';
import { basename } from 'node:path';
import { parseArgs } from 'node:util';
import { QA_DIR, startHarness } from './harness.mjs';
const { values, positionals } = parseArgs({
allowPositionals: true,
options: { at: { type: 'string', default: '1,3,6' }, probe: { type: 'string' }, width: { type: 'string', default: '1280' } },
});
if (!positionals.length) throw new Error('usage: node scripts/scene-lab.mjs <scene.lua> [--at 1,3,6] [--probe "<lua>"]');
mkdirSync(QA_DIR, { recursive: true });
const harness = await startHarness({ viewport: { width: Number(values.width), height: Math.round(Number(values.width) * 0.62) } });
try {
await harness.waitForGame();
await harness.frame().waitForFunction(() => window.powderUi, null, { timeout: 10000 });
for (const file of positionals) {
const source = readFileSync(file, 'utf8');
const name = basename(file, '.lua').replace(/^\d+-/, '');
const view = Number(/^--\s*view:\s*(\d+)/m.exec(source)?.[1] ?? 4);
const started = Date.now();
const error = await harness.frame().evaluate(({ source, view }) => {
try {
window.powderUi.loadScene({ id: 'lab', title: 'Lab', icon: '', hint: '', view, source });
return '';
} catch (e) { return String(e?.message ?? e); }
}, { source, view });
if (error) console.log(`${name}: ${error}`);
for (const seconds of values.at.split(',').map(Number)) {
const wait = started + seconds * 1000 - Date.now();
if (wait > 0) await harness.tab.waitForTimeout(wait);
const path = `${QA_DIR}lab-${name}-${seconds}s.png`;
await harness.frame().locator('#viewport').screenshot({ path });
const facts = await harness.frame().evaluate((probe) => {
const lua = window.powderUi.lua;
const parts = lua('return sim.partCount()');
let answer = '';
if (probe) { try { answer = lua(probe); } catch (e) { answer = `probe failed: ${e.message}`; } }
return { parts, answer };
}, values.probe ?? '');
console.log(`${name} ${seconds}s: ${facts.parts} particles${facts.answer ? `, ${facts.answer}` : ''} -> ${path}`);
}
}
const toasts = await harness.frame().evaluate(() => document.getElementById('toasts').textContent);
if (toasts) console.log(`notices: ${toasts}`);
if (harness.errors.length) console.log(`page errors: ${harness.errors.join(' | ')}`);
} finally {
await harness.close();
}
