/* ============================================================ layering.mjs — the assembled piece: FILM/diffusion (back) + a QFT vacuum-carpet DECK (middle, 3 spaced plate sheets) + a BUBBLE-CHAMBER event (front), composited as one image (and the literal plexi stack). Produces variations into output/layering/. Usage: node tools/layering.mjs [size] ============================================================ */ import { writeFileSync, mkdirSync } from 'node:fs'; import { carpetSVG } from '../src/qft/carpet.js'; import { generateScene } from '../src/scene/scene.js'; import { renderSVG } from '../src/render/svgVector.js'; import { paramsFromSeed as bcParams } from '../src/scene/params.js'; import { GROUPS, TOGGLES, FIXED } from '../src/ui/controls.js'; const SIZE = +(process.argv[2] || 1500); const OUT = 'output/layering'; mkdirSync(OUT, { recursive: true }); const u = SIZE / 1000; const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64'); // ---- FILM / diffusion: milky clouds (light, soft) the light diffuses through ---- function filmSVG(o = {}) { const { seed = 7, freq = 0.0016, octaves = 4, tone = [236, 228, 208], density = 0.55 } = o; const t = tone.map(v => (v / 255).toFixed(3)); return ` `; } // ---- film GRAIN veil (fine, dark, low opacity) on top ---- function grainSVG(o = {}) { const { seed = 19, tone = [38, 32, 26], amount = 0.5 } = o; const t = tone.map(v => (v / 255).toFixed(3)); return ` `; } // ---- bubble-chamber event ---- function bcSVG(seed, over = {}) { const p = { ...FIXED, ...bcParams(seed) }; for (const g of GROUPS) for (const c of g.controls) if (!(c.id in p)) p[c.id] = c.value; for (const t of TOGGLES) if (!(t.id in p)) p[t.id] = t.value; p.invert = true; p.showHeader = false; Object.assign(p, over); return renderSVG(generateScene(p), p, SIZE); } // build a 3-sheet carpet deck for a variation (hue gradient + chaos rising to front) function deck(c) { const base = { mode: 'plate', rows: 46, horizon: c.horizon ?? 0.37, wFar: 0.58, wNear: 0.7, overlap: c.overlap ?? 1.7, mound: c.mound ?? 0.35, sat: c.sat ?? 0.58, lightNear: 0.33, lightFar: 0.56, blips: c.blips ?? 1.0 }; const lerp = (a, b, t) => a + (b - a) * t; return [0, 1, 2].map(i => { const t = i / 2; // 0 back → 1 front return carpetSVG(SIZE, { ...base, salt: 'field' + i, hue: lerp(c.hueBack, c.hueFront, t), hue2: lerp(c.hueBack, c.hueFront, t) + 0.035, chaos: lerp((c.chaos ?? 0.6) * 0.8, c.chaos ?? 0.6, t) }); }); } function compose(v) { const film = dataUri(filmSVG(v.film)); const sheets = deck(v.carpet).map(dataUri); const bc = dataUri(bcSVG(v.bcSeed, v.bcOver)); const grain = dataUri(grainSVG(v.grain)); const base = v.base || 'rgb(226,219,199)'; const svg = ` `; writeFileSync(`${OUT}/${v.name}.svg`, svg); console.log(` ${v.name} (bc=${v.bcSeed})`); } // ============================================================ // Variations — film + carpet + event, ranging mood & "loudness". // Guiding thesis: calm sea, loud event. // ============================================================ const VARIATIONS = [ { name: '01_mono-calm-sea', base: 'rgb(228,221,201)', film: { seed: 3, density: 0.5, tone: [236, 228, 208] }, grain: { amount: 0.4 }, carpet: { hueBack: 0.58, hueFront: 0.50, chaos: 0.35, blips: 0.7, mound: 0.32 }, bcSeed: 'LAMBDA-2648', bcOver: { palette: 'mono' } }, { name: '02_magenta-over-teal', base: 'rgb(228,221,201)', film: { seed: 8, density: 0.5 }, grain: { amount: 0.4 }, carpet: { hueBack: 0.54, hueFront: 0.47, chaos: 0.45, blips: 1.0, mound: 0.35 }, bcSeed: 'MESON-5113', bcOver: { palette: 'magentarise', saturation: 1.05 } }, { name: '03_quiet-vast', base: 'rgb(230,224,206)', film: { seed: 12, density: 0.42, tone: [238, 231, 212] }, grain: { amount: 0.32 }, carpet: { hueBack: 0.57, hueFront: 0.52, chaos: 0.22, blips: 0.5, mound: 0.28, overlap: 1.5 }, bcSeed: 'NUCLEON-2131', bcOver: { palette: 'mono', primaries: 7, burst: 0.4, cosmics: 2, deltaRate: 0.45 } }, { name: '04_kind-verdigris', base: 'rgb(226,221,205)', film: { seed: 21, density: 0.5 }, grain: { amount: 0.4 }, carpet: { hueBack: 0.45, hueFront: 0.40, chaos: 0.5, blips: 1.1, sat: 0.42, mound: 0.35 }, bcSeed: 'HYPERON-8444', bcOver: { palette: 'kind', saturation: 1.0 } }, { name: '05_ember-warm', base: 'rgb(230,222,202)', film: { seed: 30, density: 0.52, tone: [238, 226, 204] }, grain: { amount: 0.42 }, carpet: { hueBack: 0.10, hueFront: 0.07, chaos: 0.5, blips: 1.0, sat: 0.5, mound: 0.34 }, bcSeed: 'CASCADE-2755', bcOver: { palette: 'kindrise', saturation: 1.0 } }, { name: '06_seethe-bold', base: 'rgb(227,220,200)', film: { seed: 41, density: 0.55 }, grain: { amount: 0.46 }, carpet: { hueBack: 0.55, hueFront: 0.47, chaos: 0.8, blips: 1.4, mound: 0.4, overlap: 1.9 }, bcSeed: 'MESON-5113', bcOver: { palette: 'magentarise', saturation: 1.08 } }, ]; console.log(`Layered piece — ${VARIATIONS.length} variations → ${OUT}/`); for (const v of VARIATIONS) compose(v); const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' '); writeFileSync(`${OUT}/index.html`, `Layered piece · film + QFT carpet + bubble chamber

Layered piece — film · QFT vacuum carpet · bubble-chamber event

back→front: cream ground · milky film/diffusion · 3 spaced QFT carpet sheets (back two blurred = air-gap depth of field) · bubble-chamber event (multiply) · film grain. The literal plexi stack, previewed as one image.
${VARIATIONS.map(v => `
${cap(v)}${v.bcSeed} · ${v.bcOver?.palette || 'mono'}
`).join('\n')}
`); writeFileSync(`${OUT}/m.html`, `
${VARIATIONS.map(v => `
${cap(v)}
`).join('')}
`); console.log(`contact sheets -> ${OUT}/index.html , m.html`);