Back to Powder Tool V600Billion
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.

source/vite.config.js
/**
 * 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' },
    }),
  ],
});