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.
/**
* The page's own controls, without a browser: the tool catalogue read from
* the game's Lua, the scene files (headers, pictures, and that every element
* they name exists in the pinned game), and the Lua a scene is loaded with.
* The browser checks (scripts/smoke.mjs) run all of it against the game.
*/
import assert from 'node:assert/strict';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import test from 'node:test';
import palette from '../src/save/elements.js';
import { inkFor, readTools, SECTIONS } from '../src/ui/catalog.js';
import { ANCHOR, KIT, RESET, STOP, parseScene, sceneScript } from '../src/ui/scene-meta.js';
import { PUBLIC_SOURCE } from '../scripts/public-source.mjs';
const SCENES = new URL('../src/scenes/', import.meta.url);
const sceneFiles = readdirSync(SCENES).filter((name) => name.endsWith('.lua')).sort();
const scene = (name) => parseScene(`/src/scenes/${name}`, readFileSync(new URL(name, SCENES), 'utf8'));
/** What `tools.index` answers in the game, in the page's tab-separated form (index, id, section, sort, colour, name, description). */
const LISTING = [
[2, 'DEFAULT_PT_WATR', 7, 0, 0x2030d0, 'WATR', 'Water. Conducts electricity, freezes, and extinguishes fires.'],
[1, 'DEFAULT_PT_DUST', 8, 0, 0xffe0a0, 'DUST', 'Very light dust. Flammable.'],
[5, 'DEFAULT_PT_STNE', 8, 0, 0xa0a0a0, 'STNE', 'Stone. Heavy particles, meltable.'],
[300, 'DEFAULT_WL_CNDTW', 0, 0, 0xc0c0c0, 'CONDUCTIVE WALL', 'Blocks everything. Conductive.'],
[400, 'DEFAULT_TOOL_HEAT', 13, 0, -0xff0000, 'HEAT', 'Heats the targeted element.'],
[500, 'DEFAULT_PT_LIFE_GOL', 12, 0, 0x0cac00, 'GOL', 'Game Of Life: Begin 3/Stay 23'],
[19, 'DEFAULT_PT_PLUT', 10, 0, 0x407020, 'PLUT', 'Plutonium. Heavy, fissile particles. Generates neutrons under pressure.'],
].map((row) => row.join('\t')).join('\n');
test('the tool catalogue comes from the game, in its sections and order, with readable names', () => {
const asked = [];
const tools = readTools({ lua: (code) => { asked.push(code); return LISTING; } });
assert.match(asked[0], /tools\.index/);
assert.deepEqual(tools.map((tool) => tool.id), [
'DEFAULT_WL_CNDTW', 'DEFAULT_PT_WATR', 'DEFAULT_PT_DUST', 'DEFAULT_PT_STNE',
'DEFAULT_PT_PLUT', 'DEFAULT_PT_LIFE_GOL', 'DEFAULT_TOOL_HEAT',
]);
const byId = Object.fromEntries(tools.map((tool) => [tool.id, tool]));
assert.equal(byId.DEFAULT_PT_WATR.label, 'Water');
assert.equal(byId.DEFAULT_PT_DUST.label, 'Dust');
assert.equal(byId.DEFAULT_PT_PLUT.label, 'Plutonium');
assert.equal(byId.DEFAULT_WL_CNDTW.label, 'Conductive wall');
assert.equal(byId.DEFAULT_TOOL_HEAT.label, 'Heat');
assert.equal(byId.DEFAULT_PT_LIFE_GOL.label, 'GOL', 'Game of Life rules keep their codes');
assert.equal(byId.DEFAULT_PT_WATR.colour, '#2030d0');
// A colour the game packs with its alpha bits comes out as plain RGB.
assert.equal(byId.DEFAULT_TOOL_HEAT.colour, '#010000');
assert.equal(byId.DEFAULT_PT_WATR.code, 'WATR');
});
test('every section the palette names is one of the game\'s, favourites left out', () => {
const ids = SECTIONS.map((section) => section.id).sort((a, b) => a - b);
assert.deepEqual(ids, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15]);
});
test('tool labels get black or white ink, whichever reads on their colour', () => {
assert.equal(inkFor('#ffe0a0'), '#111');
assert.equal(inkFor('#2030d0'), '#fff');
assert.equal(inkFor('#000000'), '#fff');
assert.equal(inkFor('#ffffff'), '#111');
});
test('every scene has a complete header and a picture', () => {
assert.ok(sceneFiles.length >= 8, `${sceneFiles.length} scenes`);
const ids = new Set();
for (const name of sceneFiles) {
assert.match(name, /^\d{2}-[a-z-]+\.lua$/, name);
const source = readFileSync(new URL(name, SCENES), 'utf8');
for (const key of ['title', 'icon', 'blurb', 'hint', 'view', 'thumb']) {
assert.match(source, new RegExp(`^-- ${key}: \\S`, 'm'), `${name} has no ${key}`);
}
const parsed = scene(name);
assert.ok(Number.isInteger(parsed.view) && parsed.view >= 0 && parsed.view <= 12, `${name}: view ${parsed.view}`);
assert.ok(parsed.blurb.length <= 140, `${name}: the blurb is ${parsed.blurb.length} characters`);
assert.ok(!ids.has(parsed.id), `${parsed.id} twice`);
ids.add(parsed.id);
assert.ok(existsSync(new URL(`thumbs/${parsed.id}.webp`, SCENES)), `no picture for ${parsed.id} (node scripts/scene-thumbs.mjs ${parsed.id})`);
}
assert.ok(ids.has('reactor'), 'the nuclear reactor is there');
});
test('every element a scene names exists in the pinned game', () => {
const known = new Set(palette.elements.map((row) => row[1]));
for (const name of sceneFiles) {
const source = readFileSync(new URL(name, SCENES), 'utf8');
for (const [, id] of source.matchAll(/(?:E|elem)\.DEFAULT_PT_([A-Z0-9]+)/g)) {
assert.ok(known.has(id), `${name}: DEFAULT_PT_${id} is no element of The Powder Toy ${palette.version}`);
}
}
});
test('a scene loads after a reset and its helpers, and ends in its display mode', () => {
const reactor = scene(sceneFiles.find((name) => name.endsWith('-reactor.lua')));
assert.equal(reactor.id, 'reactor');
assert.equal(reactor.title, 'Nuclear reactor');
const script = sceneScript(reactor);
assert.ok(script.startsWith(RESET));
assert.ok(script.indexOf(KIT) > script.indexOf('sim.clearSim()'));
assert.ok(script.indexOf(reactor.source) > script.indexOf(KIT));
// The anchors come from what the scene built, so they follow it.
assert.ok(script.indexOf(ANCHOR) > script.indexOf(reactor.source));
assert.match(script, new RegExp(`ren\\.useDisplayPreset\\(${reactor.view}\\)$`));
// The previous scene's scripts stop before anything else happens.
assert.ok(RESET.startsWith(STOP));
assert.match(STOP, /^if napplet_scene_step then evt\.unregister\(evt\.AFTERSIM/);
assert.match(STOP, /evt\.unregister\(evt\.TICK, napplet_scene_watch\)/);
assert.match(STOP, /napplet_scene_ended = nil$/);
});
test('a scene\'s scripts end with the scene: the step script only stops, the watch takes both off', () => {
// Only the tick event may call evt.unregister (the game refuses it after a simulation step).
const step = KIT.slice(KIT.indexOf('local function every'), KIT.indexOf('local function watch'));
const watchFn = KIT.slice(KIT.indexOf('local function watch'));
assert.doesNotMatch(step, /evt\.unregister/);
assert.match(step, /napplet_scene_ended = true return end/);
assert.match(watchFn, /evt\.unregister\(evt\.AFTERSIM, napplet_scene_step\)/);
assert.match(watchFn, /evt\.unregister\(evt\.TICK, napplet_scene_watch\)/);
// Every scene that runs scripts stands on ground walls, which become its anchors.
for (const name of sceneFiles) {
const source = readFileSync(new URL(name, SCENES), 'utf8');
if (!/\b(every|watch)\(function/.test(source)) continue;
assert.match(source, /walls\(0, 3\d\d, 611, 383\)/, `${name} runs scripts but has no ground walls to anchor them`);
}
});
test('the page names its license and the way to its source (GPL-3.0 §6, and for whoever gets the file alone)', () => {
const page = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
const about = page.slice(page.indexOf('<dialog id="about"'), page.indexOf('</dialog>', page.indexOf('<dialog id="about"')));
assert.match(about, /GNU General Public License, version 3 \(GPL-3\.0\)/);
assert.match(about, /no warranty/);
// This repository is private: the page names napplet.soy's public Git (scripts/public-source.mjs).
assert.ok(about.includes(`data-link="${PUBLIC_SOURCE}"`), 'the source button opens the public Git');
assert.ok(about.includes(`<code>${PUBLIC_SOURCE}</code>`), 'and the address is there to copy');
assert.doesNotMatch(page, /github\.com\/BIMbeamFLX/, 'no link to the private repository');
assert.match(about, /data-link="https:\/\/www\.gnu\.org\/licenses\/gpl-3\.0\.html"/);
assert.match(page, /data-action="about"/, 'the menu opens it');
});
test('a scene\'s picture is found by its id, whatever its file is numbered', () => {
const parsed = parseScene('/src/scenes/07-storm.lua', '-- title: Storm\n-- view: 4\nlocal x = 1', { '/src/scenes/thumbs/storm.webp': 'data:image/webp;base64,AA' });
assert.equal(parsed.id, 'storm');
assert.equal(parsed.thumb, 'data:image/webp;base64,AA');
assert.equal(parsed.view, 4);
assert.equal(parsed.hint, '');
});
