This commit is contained in:
2026-05-20 16:53:23 -04:00
commit dd138d5c4f
77 changed files with 973678 additions and 0 deletions

27
tools/gallery.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Render a curated set of plates into output/ — tuned toward the CERN reference.
# Usage: tools/gallery.sh [size_px] (default 2000)
set -e
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SIZE="${1:-1600}"
OUT="$ROOT/output"
mkdir -p "$OUT"
# A reference-leaning custom parameter string (busy, multi-scale, big disk, fan lines)
REF="primaries=22&burst=0.85&vdecay=4&cosmics=9&sweepers=5&bfield=1.2&eloss=0.6&pspread=0.78&deltaRate=0.92&deltaTight=0.85&shockIntensity=0.88&shockSize=0.33&shockStriations=0.78&shockY=0.5&instrument=0.5&bgEvents=6&bgIntensity=0.45&density=1.28&size=1.0&bloom=0.55&mottle=0.6&grain=0.5&vign=0.5&artifacts=0.72"
render () { # name query
"$ROOT/tools/shoot.sh" "$OUT/$1.png" "$2&size_px=$SIZE" >/dev/null
echo " output/$1.png"
}
echo "Rendering plates → output/ (${SIZE}px)…"
render "01_bebc-archival-1973" "preset=BEBC%20Archival"
render "02_bebc-gargamelle" "preset=BEBC%20Archival&seed=GARGAMELLE-4471"
render "03_dense-chaos-omega" "preset=Dense%20Chaos&seed=OMEGA-7782"
render "04_cosmic-sheet" "preset=Cosmic%20Sheet"
render "05_ref-match-strange" "$REF&seed=STRANGE-1187"
render "06_ref-match-hyperon" "$REF&seed=HYPERON-0440"
render "07_ref-match-kaon" "$REF&seed=KAON-2291&shockY=0.46&shockSize=0.36"
render "08_negative-plate" "preset=Negative%20Plate&seed=GLASS-NEG-77"
echo "done."

52
tools/inspire.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Inspiration randomizer: generate N plates from random seeds, each named by its
# seed, plus an HTML contact sheet to browse them. Because every parameter is
# derived from the seed, picking a favourite just means noting its seed and
# running: tools/render.sh <SEED>
#
# Usage: tools/inspire.sh [count] [thumb_px]
# tools/inspire.sh 32 600
set -e
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
COUNT="${1:-24}"
SIZE="${2:-600}"
OUT="$ROOT/output/inspiration"
mkdir -p "$OUT"
WORDS=(MUON KAON PION LAMBDA SIGMA XI OMEGA TAU GLUON QUARK HADRON BARYON LEPTON \
NEUTRINO BOSON STRANGE CHARM HYPERON ANTIPROTON POSITRON MESON FERMION NUCLEON \
ISOSPIN CASCADE RESONANCE PARITY GLUEBALL PENTAQUARK PHOTON)
SEEDS_FILE="$OUT/seeds.txt"
: > "$SEEDS_FILE"
echo "Generating $COUNT inspiration plates (${SIZE}px) → output/inspiration/"
for ((i = 1; i <= COUNT; i++)); do
W=${WORDS[$RANDOM % ${#WORDS[@]}]}
N=$((RANDOM % 9000 + 1000))
SEED="$W-$N"
"$ROOT/tools/shoot.sh" "$OUT/$SEED.png" "fromseed=1&seed=$SEED&size_px=$SIZE" >/dev/null
echo "$SEED" >> "$SEEDS_FILE"
printf " [%2d/%2d] %s\n" "$i" "$COUNT" "$SEED"
done
# Build the contact sheet
SHEET="$OUT/index.html"
{
echo '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Bubble Chamber — inspiration</title>'
echo '<style>body{margin:0;background:#111;color:#bbb;font:12px/1.4 ui-monospace,Menlo,monospace;padding:24px}'
echo 'h1{font-weight:400;letter-spacing:.2em;text-transform:uppercase;font-size:13px;color:#d4a574}'
echo '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:18px;margin-top:18px}'
echo 'figure{margin:0;background:#000;border:1px solid #2a2a2a}img{width:100%;display:block}'
echo 'figcaption{padding:6px 8px;display:flex;justify-content:space-between;align-items:center}'
echo '.s{color:#e8e4d8;letter-spacing:.05em}.c{color:#555;user-select:all}</style></head><body>'
echo "<h1>Bubble Chamber · inspiration · $(date +%Y-%m-%d)</h1>"
echo '<p>Click a seed to select it, then run <span class="c">tools/render.sh &lt;SEED&gt;</span> for the print master.</p>'
echo '<div class="grid">'
while IFS= read -r S; do
echo "<figure><img src=\"$S.png\" loading=\"lazy\"><figcaption><span class=\"s\">$S</span><span class=\"c\">render.sh $S</span></figcaption></figure>"
done < "$SEEDS_FILE"
echo '</div></body></html>'
} > "$SHEET"
echo "done. open: output/inspiration/index.html"

40
tools/preview.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><style>html,body{margin:0;background:#000}canvas{display:block}</style></head>
<body>
<canvas id="c"></canvas>
<script type="module">
import { generateScene } from '../src/scene/scene.js';
import { renderCanvasPhoto } from '../src/render/canvasPhoto.js';
import { GROUPS, TOGGLES, FIXED, PRESETS } from '../src/ui/controls.js';
import { paramsFromSeed } from '../src/scene/params.js';
const q = new URLSearchParams(location.search);
// fromseed=1 : derive the ENTIRE parameter set deterministically from the seed
let params;
if (q.get('fromseed') === '1') {
params = { ...FIXED, ...paramsFromSeed(q.get('seed') || 'ENTROPY-001') };
} else {
params = { ...FIXED, seed: 'ENTROPY-001' };
for (const g of GROUPS) for (const c of g.controls) params[c.id] = c.value;
for (const t of TOGGLES) params[t.id] = t.value;
if (q.get('preset') && PRESETS[q.get('preset')]) Object.assign(params, PRESETS[q.get('preset')]);
}
if (q.get('seed')) params.seed = q.get('seed');
for (const [k, v] of q) {
if (k === 'seed' || k === 'preset' || k === 'size_px') continue;
if (k in params) params[k] = (v === 'true') ? true : (v === 'false') ? false : (isNaN(+v) ? v : +v);
}
const SIZE = +(q.get('size_px') || 1200);
const c = document.getElementById('c');
c.width = c.height = SIZE;
const ctx = c.getContext('2d', { willReadFrequently: true });
const scene = generateScene(params);
renderCanvasPhoto(ctx, SIZE, SIZE, scene, params, { preview: q.get('hq') !== '1' });
document.title = 'done';
window.__done = true;
</script>
</body>
</html>

33
tools/render-pdf.mjs Normal file
View File

@@ -0,0 +1,33 @@
/* Render a scene to a print-ready CMYK PDF from the CLI.
Two modes:
node tools/render-pdf.mjs --seed SEED [outfile] [pagePt] (seed → all params)
node tools/render-pdf.mjs [preset] [seed] [outfile] (legacy preset mode)
*/
import { writeFileSync } from 'node:fs';
import { generateScene } from '../src/scene/scene.js';
import { buildPDF } from '../src/render/pdf.js';
import { paramsFromSeed } from '../src/scene/params.js';
import { GROUPS, TOGGLES, FIXED, PRESETS } from '../src/ui/controls.js';
const argv = process.argv.slice(2);
let params, out, page = 1728;
if (argv[0] === '--seed') {
const seed = argv[1] || 'ENTROPY-001';
params = { ...FIXED, ...paramsFromSeed(seed) };
out = argv[2];
if (argv[3]) page = +argv[3];
} else {
const [preset, seed, o] = argv;
params = { ...FIXED, seed: seed || 'ENTROPY-001' };
for (const g of GROUPS) for (const c of g.controls) params[c.id] = c.value;
for (const t of TOGGLES) params[t.id] = t.value;
if (preset && PRESETS[preset]) Object.assign(params, PRESETS[preset]);
if (seed) params.seed = seed;
out = o;
}
const pdf = buildPDF(generateScene(params), params, page);
const file = out || `/tmp/bc-${params.seed}.pdf`;
writeFileSync(file, pdf);
console.log(`PDF -> ${file} (${(pdf.length / 1024).toFixed(0)} KB)`);

33
tools/render-svg.mjs Normal file
View File

@@ -0,0 +1,33 @@
/* Render a scene to SVG from the CLI (shares the scene module).
Two modes:
node tools/render-svg.mjs --seed SEED [outfile] [sizePx] (seed → all params)
node tools/render-svg.mjs [preset] [seed] [outfile] (legacy preset mode)
*/
import { writeFileSync } from 'node:fs';
import { generateScene } from '../src/scene/scene.js';
import { renderSVG } from '../src/render/svgVector.js';
import { paramsFromSeed } from '../src/scene/params.js';
import { GROUPS, TOGGLES, FIXED, PRESETS } from '../src/ui/controls.js';
const argv = process.argv.slice(2);
let params, out, size = 4800;
if (argv[0] === '--seed') {
const seed = argv[1] || 'ENTROPY-001';
params = { ...FIXED, ...paramsFromSeed(seed) };
out = argv[2];
if (argv[3]) size = +argv[3];
} else {
const [preset, seed, o] = argv;
params = { ...FIXED, seed: seed || 'ENTROPY-001' };
for (const g of GROUPS) for (const c of g.controls) params[c.id] = c.value;
for (const t of TOGGLES) params[t.id] = t.value;
if (preset && PRESETS[preset]) Object.assign(params, PRESETS[preset]);
if (seed) params.seed = seed;
out = o;
}
const svg = renderSVG(generateScene(params), params, size);
const file = out || `/tmp/bc-${params.seed}.svg`;
writeFileSync(file, svg);
console.log(`SVG -> ${file} (${(svg.length / 1024).toFixed(0)} KB)`);

29
tools/render.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Print-master renderer for a chosen seed. Produces, into output/masters/:
# <SEED>.svg vector geometry — INFINITE resolution, the true master
# <SEED>.pdf vector CMYK — print-shop ready, 24" page
# <SEED>_<size>.png photographic — carries grain / bloom / stain
#
# Every parameter derives from the seed, so this is identical to the inspiration
# thumbnail of the same seed, just at full resolution.
#
# Usage: tools/render.sh <SEED> [png_px]
# tools/render.sh STRANGENESS-7
# tools/render.sh STRANGENESS-7 12000
set -e
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SEED="$1"
SIZE="${2:-9000}"
if [ -z "$SEED" ]; then echo "usage: tools/render.sh <SEED> [png_px]"; exit 1; fi
OUT="$ROOT/output/masters"
mkdir -p "$OUT"
echo "Rendering master for seed: $SEED"
echo " • vector geometry (infinite res)…"
node "$ROOT/tools/render-svg.mjs" --seed "$SEED" "$OUT/$SEED.svg" 6000 | sed 's/^/ /'
echo " • vector CMYK PDF (print shop)…"
node "$ROOT/tools/render-pdf.mjs" --seed "$SEED" "$OUT/$SEED.pdf" | sed 's/^/ /'
echo " • photographic raster (${SIZE}px)…"
"$ROOT/tools/shoot.sh" "$OUT/${SEED}_${SIZE}.png" "fromseed=1&seed=$SEED&size_px=$SIZE" | sed 's/^/ /'
echo "done → output/masters/ ($SEED .svg/.pdf/_${SIZE}.png)"

37
tools/shoot.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Render the artwork headlessly for the compare loop.
# Usage: tools/shoot.sh [outfile] [query]
# tools/shoot.sh /tmp/bc.png "preset=BEBC%20Archival&size_px=1200"
# tools/shoot.sh /tmp/bc.png "seed=KAON-0088&shockIntensity=0.9"
set -e
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="${1:-/tmp/bc.png}"
QUERY="${2:-size_px=1200}"
PORT=8731
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
# start a static server if not already up
if ! curl -s "http://localhost:$PORT/tools/preview.html" >/dev/null 2>&1; then
(cd "$ROOT" && python3 -m http.server "$PORT" >/tmp/bc_server.log 2>&1 &)
sleep 1
fi
# pull size for the window
SIZE=$(echo "$QUERY" | sed -n 's/.*size_px=\([0-9]*\).*/\1/p'); SIZE="${SIZE:-1200}"
# scale the virtual-time budget with resolution; cap with a hard watchdog so a
# slow/hung render can never block forever.
BUDGET=4000; WATCHDOG=45
if [ "$SIZE" -ge 1800 ]; then BUDGET=8000; WATCHDOG=90; fi
"$CHROME" --headless=new --disable-gpu --hide-scrollbars \
--force-device-scale-factor=1 --virtual-time-budget="$BUDGET" \
--run-all-compositor-stages-before-draw \
--screenshot="$OUT" --window-size="$SIZE,$SIZE" \
"http://localhost:$PORT/tools/preview.html?$QUERY" 2>/dev/null &
CPID=$!
( sleep "$WATCHDOG"; kill -9 "$CPID" 2>/dev/null ) & WPID=$!
wait "$CPID" 2>/dev/null
kill "$WPID" 2>/dev/null || true
if [ -f "$OUT" ]; then echo "shot -> $OUT ($QUERY)"; else echo "FAILED -> $OUT ($QUERY)"; exit 1; fi