Files
bubblechambersimart/tools/perspective3.mjs

130 lines
8.5 KiB
JavaScript
Raw Normal View History

2026-06-02 19:17:19 -04:00
/* ============================================================
perspective3.mjs 12 compositions built through the REFACTORED
grouped schema (renderComposition). Shows off what the refactor
unlocked: disk & collision decoupled (independent centre / rotation
/ scale), frame-level fiduciary arrows that span the frame to find a
distant event, and per-layer transforms on the fields themselves.
Usage: node tools/perspective3.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { renderComposition } from '../src/compose/composition.js';
const SIZE = +(process.argv[2] || 1500);
const OUT = 'output/layering/perspective3';
mkdirSync(OUT, { recursive: true });
const F = Math.PI * 2, D = Math.PI / 180;
const merge = (a, b) => {
const o = { ...a };
for (const k in b) o[k] = (b[k] && typeof b[k] === 'object' && !Array.isArray(b[k])) ? merge(a[k] || {}, b[k]) : b[k];
return o;
};
const BASE = {
size: SIZE, seed: 'MESON-5113',
background: { color: 'rgb(229,222,203)', film: { opacity: 0.6, density: 0.5, seed: 8 }, aging: { opacity: 0.5, scratches: 5, dust: 0.45, foxing: 0.5, seed: 5 }, grain: { opacity: 0.42, intensity: 0.42, seed: 19 } },
fieldSea: { enabled: true, transform: { x: 0, y: 0, rotation: 0, scale: 1 }, seed: 'VACUUM-5113', color: { hueBack: 0.54, hueFront: 0.47, sat: 0.6 }, layers: 3, blurPerLayer: [2.6, 1.1, 0], chaos: 0.3, blips: 0.7, mound: 0.3, horizonY: 0.36, lines: 46 },
fieldGrid: { enabled: true, pos: 'over', transform: { x: 0, y: 0, rotation: 0, scale: 1 }, color: { hue: 0.56, hue2: 0.5, sat: 0.26, lightNear: 0.44, lightFar: 0.66 }, opacity: 0.38, vp: [0, 0], dir: 0, spread: F, rays: 30, depthLines: 17, rMin: 0.04, rMax: 3.0, ripple: { amp: 0.07, ampRad: 0.06, freqR: 2.4, freqA: 5, phase: 0 }, layers: 2, blurPerLayer: [1.1, 0] },
disk: { enabled: true, transform: { x: 0, y: -0.25, rotation: 0, scale: 0.95 }, hue: 0.06, sat: 0.82, size: 0.16, pressure: 0.85 },
bubble: { enabled: true, transform: { x: 0.28, y: -0.1, rotation: 12, scale: 0.82 }, palette: 'magentarise', saturation: 1.05, primaries: 18, sweepers: 5, eloss: 0.34 },
fiduciaries: { enabled: true, label: 'No 001', pencil: '#39312a', width: 1.0, arrow: true },
};
const mk = (name, over) => ({ name, comp: merge(BASE, over) });
const VARIATIONS = [
// 1 — the WITNESS and the EVENT: a quiet sun, the collision flung to a far
// corner; the hand's arrow has to reach all the way across to name it.
mk('01_witness-and-event', {
disk: { transform: { x: -0.05, y: -0.3, scale: 0.62 }, hue: 0.08 },
bubble: { transform: { x: 0.52, y: 0.44, rotation: -8, scale: 0.5 } },
fiduciaries: { from: [-0.02, -0.22], label: 'No 001', caption: 'good event' },
}),
// 2 — ECLIPSE: an enormous low-pressure sun, the event tucked at its rim.
mk('02_eclipse', {
disk: { transform: { x: 0, y: -0.06, scale: 1.55 }, hue: 0.0, sat: 0.9, size: 0.18, pressure: 0.95 },
bubble: { transform: { x: 0.34, y: -0.16, scale: 0.46 } },
fieldGrid: { opacity: 0.3, vp: [0, -0.05] },
}),
// 3 — INVERSION: sun sunk into the sea, the collision risen high above it.
mk('03_sunk-sun-risen-event', {
disk: { transform: { x: -0.02, y: 0.4, scale: 0.8 }, hue: 0.05 },
bubble: { transform: { x: 0.05, y: -0.46, rotation: 20, scale: 0.7 } },
fieldGrid: { pos: 'behind', opacity: 0.34 },
}),
// 4 — TILTED ARCHIVE: the whole event canted 32°; studied with corners + caption.
mk('04_tilted-archive', {
disk: { transform: { x: -0.32, y: -0.22, scale: 0.66 }, hue: 0.11 },
bubble: { transform: { x: 0.22, y: 0.0, rotation: 32, scale: 0.82 } },
fiduciaries: { corners: true, caption: 'check θ', from: [0.5, -0.35] },
}),
// 5 — VORTEX PULL: the grid is a whirlpool; the collision caught at its mouth,
// a small far sun looking on.
mk('05_vortex-pull', {
fieldGrid: { vp: [0.08, -0.04], color: { hue: 0.5, hue2: 0.55, sat: 0.34, lightNear: 0.42, lightFar: 0.64 }, opacity: 0.46, ripple: { amp: 0.11, ampRad: 0.09, freqR: 2.9, freqA: 7, phase: 0.8 }, rMax: 2.6, rays: 34 },
disk: { transform: { x: -0.42, y: -0.34, scale: 0.55 }, hue: 0.95 },
bubble: { transform: { x: 0.08, y: -0.03, scale: 0.72 } },
}),
// 6 — GRID ASKEW: the depth grid itself rotated + zoomed (per-layer transform).
mk('06_grid-askew', {
fieldGrid: { transform: { x: 0.1, y: -0.05, rotation: 16, scale: 1.15 }, vp: [-0.35, -0.2], spread: 90 * D, dir: 35 * D, rMax: 3.6, color: { hue: 0.6, hue2: 0.6, sat: 0.1, lightNear: 0.46, lightFar: 0.66 } },
disk: { transform: { x: 0.05, y: -0.26, scale: 0.8 }, hue: 0.06 },
bubble: { transform: { x: -0.18, y: 0.06, rotation: -10, scale: 0.78 } },
}),
// 7 — CORNER DAWN: a rose sun in the upper-left, the event mid-frame, long reach.
mk('07_corner-dawn-rose', {
disk: { transform: { x: -0.56, y: -0.52, scale: 0.5 }, hue: 0.95, sat: 0.7, pressure: 0.5 },
bubble: { transform: { x: 0.16, y: 0.08, scale: 0.86 } },
fiduciaries: { from: [-0.5, -0.4] },
}),
// 8 — SCALE RHYME: two small foci mirrored across the frame — one teal sun,
// one event rotated 180°, the rhyme across the void.
mk('08_scale-rhyme-mirror', {
disk: { transform: { x: -0.42, y: -0.12, scale: 0.5 }, hue: 0.5, sat: 0.6 },
bubble: { transform: { x: 0.42, y: 0.12, rotation: 180, scale: 0.5 } },
fieldGrid: { opacity: 0.3 },
}),
// 9 — MEASURED DAWN: classic — sun on the horizon, studied event, registration
// corners, a faint cool grid. The archive at its calmest.
mk('09_measured-dawn', {
disk: { transform: { x: 0, y: -0.26, scale: 0.72 }, hue: 0.07 },
bubble: { transform: { x: 0.3, y: -0.08, rotation: 6, scale: 0.74 } },
fiduciaries: { corners: true, caption: 'good event' },
fieldGrid: { opacity: 0.32 },
}),
// 10 — COLD CATHEDRAL: a violet vaulting grid from on high (behind), a teal
// sun, a verdigris sea. The numinous, gone cold.
mk('10_cold-cathedral', {
fieldSea: { color: { hueBack: 0.46, hueFront: 0.42, sat: 0.42 }, chaos: 0.28 },
fieldGrid: { pos: 'behind', vp: [0, -0.66], dir: 95 * D, spread: 150 * D, color: { hue: 0.7, hue2: 0.66, sat: 0.3, lightNear: 0.44, lightFar: 0.66 }, opacity: 0.5, ripple: { amp: 0.1, ampRad: 0.08, freqR: 3.0, freqA: 8, phase: 1.2 } },
disk: { transform: { x: 0.05, y: -0.24, scale: 0.78 }, hue: 0.5, sat: 0.6 },
bubble: { transform: { x: -0.22, y: -0.06, scale: 0.8 } },
}),
// 11 — GIANT GRID, TINY EVENT: the coordinate-system dwarfs the collision;
// the field zoomed huge, a small ember far off.
mk('11_giant-grid-tiny-event', {
fieldGrid: { transform: { scale: 1.4, x: -0.1 }, opacity: 0.42, vp: [0.15, 0.05], ripple: { amp: 0.09, ampRad: 0.07, freqR: 2.6, freqA: 6, phase: 0.4 } },
disk: { transform: { x: 0.46, y: -0.42, scale: 0.42 }, hue: 0.0, pressure: 0.9 },
bubble: { transform: { x: 0.4, y: -0.3, scale: 0.4 } },
fiduciaries: { from: [0.1, -0.2] },
}),
// 12 — DISSOLVING COORDINATES: the grid a warm near-paper whisper to the edges;
// the event the only firm thing. (white pencil — backlit-intent, faint unlit.)
mk('12_dissolving-coordinates', {
fieldGrid: { color: { hue: 0.1, hue2: 0.08, sat: 0.16, lightNear: 0.5, lightFar: 0.68 }, opacity: 0.26, rMax: 3.7, ripple: { amp: 0.12, ampRad: 0.1, freqR: 3.1, freqA: 7, phase: 1.0 } },
disk: { transform: { x: 0, y: -0.24, scale: 0.74 }, hue: 0.04, pressure: 0.85 },
bubble: { transform: { x: 0.26, y: -0.1, scale: 0.78 } },
fiduciaries: { pencil: '#f4efe6', caption: 'No 001' },
}),
];
console.log(`perspective3 — ${VARIATIONS.length} schema-built compositions → ${OUT}/`);
for (const v of VARIATIONS) {
writeFileSync(`${OUT}/${v.name}.svg`, renderComposition(v.comp, SIZE));
console.log(` ${v.name}`);
}
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
writeFileSync(`${OUT}/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>perspective3 · schema-built · decoupled</title>
<style>html,body{margin:0;background:#1a1a1a}.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('')}</div></body></html>`);
console.log(`contact sheet -> ${OUT}/m.html`);