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 Powder Toy napplet, built the way napplet.soy describes: Vite plus
* @napplet/vite-plugin in single-file mode, which inlines the bundle into
* dist/index.html and writes the kind 35129 manifest template next to it.
*
* On top, `powderUpstream` puts the game into the page: the patched
* Emscripten glue as a classic script (it defines `create_powder` before the
* module runs) and the wasm, gzip-compressed and base64-encoded, in an inert
* script element. Nothing is fetched at run time; the frame has no network.
* The wasm is upstream's source built without threads (scripts/build-wasm.sh),
* so the page runs in any `sandbox="allow-scripts"` frame: no
* SharedArrayBuffer, no workers, no cross-origin isolation.
*
* `npm run dock` then copies dist into a nappelin.com checkout
* (apps/hangar/public/napplets/powder-toy) and writes provenance.json.
*/
import { defineConfig } from 'vite';
import { nip5aManifest } from '@napplet/vite-plugin';
import { patchedGlue, upstream, wasmGzipBase64 } from './scripts/upstream.mjs';
export const D_TAG = 'powder-toy';
export const TITLE = 'Powder Tool V600Billion';
export const DESCRIPTION = `The Powder Toy ${upstream.version}, the falling-sand physics sandbox. `
+ 'Saves stay with you; shared saves, votes and comments travel over Nostr under your name.';
/** What the napplet asks the host for (the Hangar's catalog entry, nappelin.com apps/hangar/src/catalog.ts, must say the same). */
export const REQUIRES = ['identity', 'link', 'outbox', 'storage'];
/**
* The document's own policy, on top of the host's: no network at all (the
* game's own calls to powdertoy.co.uk are answered in the page, src/server/),
* no workers, WebAssembly, no eval. napplet.soy's player policy is the same
* or looser in every directive, so the page asks for nothing extra there.
*/
const CSP = [
"default-src 'none'",
"script-src 'unsafe-inline' 'wasm-unsafe-eval'",
"worker-src 'none'",
"style-src 'unsafe-inline'",
"img-src data: blob:",
"connect-src 'none'",
"base-uri 'none'",
"form-action 'none'",
].join('; ');
function powderUpstream() {
let isBuild = false;
return {
name: 'powder-toy-upstream',
configResolved(config) { isBuild = config.command === 'build'; },
transformIndexHtml: {
order: 'post',
handler(html) {
const glue = patchedGlue();
const wasm = wasmGzipBase64();
const payload = `<script type="application/octet-stream" id="powder-wasm" data-encoding="gzip+base64">${wasm}</script>\n`
+ `<script id="powder-glue">\n${glue}\n</script>\n`;
const body = html.replace('</body>', () => `${payload}</body>`);
const tags = [
{ tag: 'meta', attrs: { name: 'napplet-requires', content: REQUIRES.join(',') }, injectTo: 'head' },
{ tag: 'meta', attrs: { name: 'powder-toy-upstream', content: `${upstream.version} ${upstream.commit}` }, injectTo: 'head' },
];
// The dev server loads modules from 'self', which this policy would block.
if (isBuild) tags.unshift({ tag: 'meta', attrs: { 'http-equiv': 'Content-Security-Policy', content: CSP }, injectTo: 'head-prepend' });
return { html: body, tags };
},
},
};
}
export default defineConfig({
base: './',
build: {
outDir: 'dist',
emptyOutDir: true,
target: 'es2022',
// A fetch()-based preload helper would hit connect-src 'none'.
modulePreload: false,
cssCodeSplit: false,
assetsInlineLimit: 100_000_000,
reportCompressedSize: false,
},
plugins: [
powderUpstream(),
nip5aManifest({
nappletType: D_TAG,
title: TITLE,
description: DESCRIPTION,
artifactMode: 'single-file',
// Reviewed: no incoming INC role and no outgoing intent.
archetypes: [],
requires: { infer: true, explicit: REQUIRES, mode: 'error' },
}),
],
});
