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

178 lines
12 KiB
JavaScript
Raw Permalink Normal View History

/* ============================================================
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`);