Files
bubblechambersimart/tools/qft-bc-magenta.mjs

178 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
qft-bc-magenta.mjs — sketch 02 of the JOINED series.
Built on 78_final-lively-magenta-cream (seed MESON-5113,
palette magentarise, cream paper). A THIN-LINE but present
QFT field is slipped BETWEEN the cream ground and the bubble
chamber: cream paper → thin QFT lattice → magentarise event
(multiply on top). The bubble-chamber layer is constant; each
frame varies the QFT geometry AND its hue family, chosen to
converse with the magenta tracks + burnt-orange disk + cream.
Usage: node tools/qft-bc-magenta.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { generateQFTScene } from '../src/qft/scene.js';
import { paramsFromSeed as qftParams } from '../src/qft/params.js';
import { renderQFTSVG } from '../src/qft/renderer.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] || 1700);
const OUT = 'output/qft-bc/sketch02';
mkdirSync(OUT, { recursive: true });
// F(hueStart,hueEnd,sat,light,opacity[,stroke]) — thin strokes throughout.
const F = (h0, h1, s, l, o, st = 0.6) => ({ hueStart: h0, hueEnd: h1, saturation: s, lightness: l, opacity: o, stroke: st });
const OFF = F(0, 0, 0, 0.5, 0);
const paper = (flat, gi = [10, 9, 8], go = [-16, -16, -16]) => ({
flat, glowIn: [flat[0] + gi[0], flat[1] + gi[1], flat[2] + gi[2]], glowOut: [flat[0] + go[0], flat[1] + go[1], flat[2] + go[2]],
});
const W = (x, y, amplitude, sigma) => ({ x, y, amplitude, sigma });
const VX = (x, y, strength, sigma) => ({ x, y, strength, sigma });
const SW = (kx, ky, amplitude, phase = 0) => ({ kx, ky, amplitude, phase });
const R = (x, y, count = 6, r0 = 0.08, dR = 0.10, propagator = 'photon') => ({ x, y, count, r0, dR, propagator });
const CREAM = [236, 228, 208]; // pale cream so the BC's own paper carries the tone
// thin-line QFT base: light vignette/glow so the ground stays flat under the BC.
const QBASE = {
substrate: 'cream', paperOverride: paper(CREAM), vignOverride: [120, 110, 90],
showHeader: false, glow: 0.18, vign: 0.10, stroke: 0.7, segmentsPerEdge: 8,
photonCyclesPerUnit: 12, linkCurvature: 0.18,
};
// constant bubble-chamber layer — the 78 piece (lively magenta on cream).
function bcLayer() {
const p = { ...FIXED, ...bcParams('MESON-5113') };
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.palette = 'magentarise'; p.paperTone = 'cream'; p.invert = true;
p.showHeader = false; p.saturation = 1.05; // a touch livelier
return p;
}
const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
function composite(v) {
const qp = { ...qftParams(v.qftSeed), ...QBASE, ...(v.qftOver || {}) };
const bp = bcLayer();
const qftSvg = renderQFTSVG(generateQFTScene(qp), qp, SIZE);
const bcSvg = renderSVG(generateScene(bp), bp, SIZE);
const s = v.qScale ?? 1, qw = SIZE * s, qh = SIZE * s;
const qx = (SIZE - qw) / 2 + (v.qDx || 0), qy = (SIZE - qh) / 2 + (v.qDy || 0);
const out =
`<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<rect width="${SIZE}" height="${SIZE}" fill="rgb(${CREAM.join(',')})"/>
<image x="${qx.toFixed(0)}" y="${qy.toFixed(0)}" width="${qw.toFixed(0)}" height="${qh.toFixed(0)}" href="${dataUri(qftSvg)}" opacity="${v.qOpacity ?? 1}"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${dataUri(bcSvg)}" style="mix-blend-mode:multiply"/>
</svg>`;
writeFileSync(`${OUT}/${v.name}.svg`, out);
console.log(` ${v.name} (qft=${v.qftSeed}/${qp.archetype})`);
return out;
}
// ============================================================
// 10 hue families × QFT geometries, all under the same magenta event.
// ============================================================
const VARIATIONS = [
// 1 — TONAL ECHO. Field in the same magenta family, paler & desaturated:
// the trace's colour, whispered, as its own substrate.
{ name: '01_tonal-magenta-echo', qftSeed: 'FEYNMAN-7167',
qftOver: { cubicN: 2, e8Count: 3, e8OriginRadius: 0.6, linkCount: 6,
fields: { cubic: F(0.86, 0.90, 0.46, 0.50, 0.60, 0.55), schlegel: F(0.80, 0.84, 0.42, 0.48, 0.52, 0.55),
e8: F(0.90, 0.94, 0.52, 0.50, 0.65, 0.6), ripple: OFF, links: F(0.92, 0.88, 0.58, 0.48, 0.66, 0.8) } } },
// 2 — THE COMPLEMENT. Teal/viridian — magenta's opposite — for maximum
// vibrance under the warm event. A swirling vortex lattice.
{ name: '02_magenta-teal-duotone', qftSeed: 'GAUGE-2046',
qftOver: { cubicN: 2, e8Count: 1, e8OriginRadius: 0.64, linkCount: 6, vortices: [VX(0, 0, 0.8, 0.3)],
fields: { cubic: F(0.47, 0.50, 0.55, 0.46, 0.58, 0.55), schlegel: F(0.50, 0.46, 0.50, 0.44, 0.50, 0.6),
e8: F(0.45, 0.48, 0.55, 0.48, 0.55, 0.6), ripple: OFF, links: F(0.48, 0.44, 0.60, 0.46, 0.62, 0.8) } } },
// 3 — EMBER UNDERGLOW. Gold→copper field, picking up the burnt-orange disk:
// the event smouldering on a bed of its own embers. Nautilus rosettes.
{ name: '03_ember-gold-underglow', qftSeed: 'LATTICE-1003',
qftOver: { cubicN: 2, e8Style: 'nautilus', e8Count: 3, nautilusTurns: 2.6, nautilusPerTurn: 14, nautilusGrowth: 0.2, linkCount: 6,
fields: { cubic: F(0.10, 0.08, 0.62, 0.46, 0.62, 0.55), schlegel: F(0.08, 0.06, 0.55, 0.44, 0.52, 0.55),
e8: F(0.12, 0.07, 0.72, 0.46, 0.70, 0.6), ripple: OFF, links: F(0.07, 0.05, 0.82, 0.44, 0.72, 0.8) } } },
// 4 — COOL RECEDE. Violet→indigo, the cool neighbour of magenta — the field
// steps back into shadow while the warm trace advances. Big tesseract.
{ name: '04_violet-indigo-recede', qftSeed: 'PROPAGATOR-2755',
qftOver: { cubicN: 1, schlegelScale: 1.3, schlegelOuterR: 0.86, e8Count: 2, linkCount: 5,
fields: { cubic: F(0.70, 0.74, 0.45, 0.50, 0.48, 0.5), schlegel: F(0.74, 0.78, 0.50, 0.46, 0.55, 0.6),
e8: F(0.68, 0.72, 0.45, 0.50, 0.50, 0.6), ripple: OFF, links: F(0.76, 0.72, 0.50, 0.48, 0.55, 0.8) } } },
// 5 — VERDIGRIS. Desaturated sea-green patina — aged copper plate — under a
// Chladni standing-wave membrane. Quiet, oxidised, archival.
{ name: '05_verdigris-chladni', qftSeed: 'VACUUM-5113',
qftOver: { cubicN: 2, e8Count: 0, linkCount: 3, standingWaves: [SW(6, 1, 0.04), SW(1, 6, 0.04)],
fields: { cubic: F(0.44, 0.48, 0.30, 0.54, 0.55, 0.55), schlegel: F(0.46, 0.50, 0.26, 0.52, 0.45, 0.55),
e8: OFF, ripple: OFF, links: F(0.42, 0.46, 0.35, 0.52, 0.50, 0.75) } } },
// 6 — SPLIT-COMPLEMENT. Teal lattice, GOLD propagator links (tied to the
// disk accent) — a two-colour field that brackets the magenta on both sides.
{ name: '06_teal-lattice-gold-links', qftSeed: 'FEYNMAN-7167',
qftOver: { cubicN: 2, e8Count: 3, e8OriginRadius: 0.58, linkCount: 9, linkCurvature: 0.3,
fields: { cubic: F(0.49, 0.52, 0.45, 0.50, 0.52, 0.55), schlegel: F(0.50, 0.47, 0.42, 0.48, 0.45, 0.55),
e8: F(0.11, 0.08, 0.55, 0.55, 0.55, 0.6), ripple: OFF, links: F(0.12, 0.07, 0.70, 0.55, 0.7, 0.85) } } },
// 7 — ROSE GHOST. The thinnest, palest field — a barely-there dusty pink
// lattice, present only as a breath. Sparse. Contemplative.
{ name: '07_rose-ghost-quiet', qftSeed: 'GAUGE-2046',
qftOver: { cubicN: 1, e8Count: 1, e8OriginRadius: 0.66, linkCount: 4,
fields: { cubic: F(0.93, 0.90, 0.28, 0.66, 0.40, 0.45), schlegel: F(0.90, 0.94, 0.24, 0.64, 0.34, 0.45),
e8: F(0.92, 0.96, 0.30, 0.66, 0.42, 0.5), ripple: OFF, links: F(0.94, 0.90, 0.34, 0.64, 0.42, 0.65) } } },
// 8 — PALE SPECTRAL. A faint rainbow lattice — cubic cool-blue, schlegel
// violet, e8 gold, links rose: an earned-colour field, softened to a haze.
{ name: '08_pale-spectral-lattice', qftSeed: 'LATTICE-1003',
qftOver: { cubicN: 2, e8Count: 3, e8OriginRadius: 0.6, linkCount: 7,
fields: { cubic: F(0.55, 0.60, 0.40, 0.58, 0.46, 0.5), schlegel: F(0.72, 0.78, 0.42, 0.54, 0.46, 0.55),
e8: F(0.11, 0.14, 0.50, 0.58, 0.50, 0.6), ripple: OFF, links: F(0.93, 0.88, 0.45, 0.58, 0.5, 0.8) } } },
// 9 — CYAN BLUEPRINT. Pale cyan/blue draughtsman's lines with expanding
// ripple wavefronts — the schematic under the photograph.
{ name: '09_cyan-blueprint-ripples', qftSeed: 'PROPAGATOR-2755',
qftOver: { cubicN: 2, e8Count: 0, linkCount: 4, ripples: [R(0, 0, 5, 0.1, 0.11)],
fields: { cubic: F(0.54, 0.57, 0.45, 0.52, 0.52, 0.5), schlegel: F(0.56, 0.53, 0.42, 0.50, 0.45, 0.55),
e8: OFF, ripple: F(0.55, 0.58, 0.50, 0.54, 0.55, 0.6), links: F(0.54, 0.57, 0.50, 0.50, 0.55, 0.8) } } },
// 10 — COMMUNION. The field sweeps the EXACT purple→magenta→pink band of the
// trace family, at lowest saturation — field and trace are one colour,
// one chemistry; only the burnt-orange disk stands apart. ★
{ name: '10_trace-family-communion', qftSeed: 'FEYNMAN-7167',
qftOver: { cubicN: 2, e8Style: 'nautilus', e8Count: 3, nautilusTurns: 2.8, nautilusPerTurn: 16, nautilusGrowth: 0.2, linkCount: 7,
fields: { cubic: F(0.82, 0.90, 0.40, 0.50, 0.60, 0.55), schlegel: F(0.79, 0.86, 0.38, 0.48, 0.54, 0.55),
e8: F(0.88, 0.95, 0.44, 0.50, 0.64, 0.6), ripple: OFF, links: F(0.90, 0.82, 0.50, 0.48, 0.66, 0.8) } } },
];
console.log(`Compositing ${VARIATIONS.length} magenta-cream QFT×BC plates → ${OUT}/`);
for (const v of VARIATIONS) composite(v);
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
const idx = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>QFT × Bubble Chamber · sketch 02 · magenta-cream</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#d6a5c4}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#180e16;padding:14px 18px;border-left:3px solid #d6a5c4;margin:18px 0}</style></head><body>
<h1>QFT × Bubble Chamber · sketch 02 — thin field between cream & the magenta event</h1>
<div class="notes">Built on <b>78_final-lively-magenta-cream</b> (MESON-5113 · magentarise · cream). A thin-line but
present QFT lattice is slipped between the cream ground and the bubble chamber (multiply on top). The event is constant;
each frame varies the QFT geometry and a hue family chosen to converse with the magenta tracks + burnt-orange disk.</div>
<div class=grid>
${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}<small>field: ${v.qftSeed}</small></figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/index.html`, idx);
const m = `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/m.html`, m);
console.log(`contact sheets -> ${OUT}/index.html , m.html`);