/* ============================================================ refined-backlit4.mjs — ONE SUN SETTING, GOLDEN. The whole composition is built on φ (1.6180…): · CANVAS a golden rectangle, W = φ·H (landscape) · HORIZON the waterline splits the height 1/φ : 1 → y = W·0 (sky = 0.382 H · sea = 0.618 H, ratio 1/φ) · DISK the sun's centre sits on the LEFT golden line (0.382 W) at the golden division of the sky; its radius is 0.382 of the sky band · TRACES the event vertex sits on the RIGHT golden line (0.618 W) where it meets the horizon · WAVES amplitude:gap = φ · near:far width = φ · the three stacked sheets stepped in opacity by 1/φ Each iteration re-rolls only the bubble-trace seed; the golden armature is identical. Grain + distressing stay on. Usage: node tools/refined-backlit4.mjs [width] → fable/REFINED_Backlit4/ ============================================================ */ 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'; import { makeRng } from '../src/rng.js'; const PHI = (1 + Math.sqrt(5)) / 2; // 1.6180339887… const IP = 1 / PHI; // 0.6180… (long section) const IP2 = 1 / (PHI * PHI); // 0.3820… (short section) const W = +(process.argv[2] || 1001); const H = Math.round(W * PHI); // golden rectangle, PORTRAIT (H = φ·W) const OUT = 'fable/REFINED_Backlit4'; mkdirSync(OUT, { recursive: true }); const u = Math.min(W, H) / 1000; // stroke unit keyed to the short side (width) const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64'); /* ---- golden armature (px) ---- */ const SEED = 'MESON-5113'; const BASE = 'rgb(227,220,200)'; const HORIZON_F = IP2; // waterline at 0.382 H → sky:sea = 1/φ const HY = HORIZON_F * H; const SKY_H = HY; // sky band height const S_SCALE = (Math.min(W, H) / 2) * (1 - 0.02); // renderSVG's isotropic scene→px scale const XL = IP2 * W; // left golden line 0.382 W const XR = IP * W; // right golden line 0.618 W const SUN = { x: XL, y: IP * SKY_H }; // sun centre: left line, 0.618 down the sky const SUN_R = IP2 * SKY_H; // sun radius = 0.382 of the sky band const VERTEX = { x: XR, y: HY }; // event vertex: right line ∩ horizon /* ---- film / grain / aging, now golden-canvas sized ---- */ 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 ` `; } function grainSVG(o = {}) { const { seed = 19, tone = [38, 32, 26], amount = 0.5 } = o; const t = tone.map(v => (v / 255).toFixed(3)); return ` `; } function agingSVG(o = {}) { const { seed = 5, scratches = 8, dust = 0.6, foxing = 0.62, tone = [60, 48, 34] } = o; const t = tone.map(v => (v / 255).toFixed(3)); const rng = makeRng(SEED, 'aging' + seed); let lines = ''; for (let i = 0; i < scratches; i++) { const x = rng() * W, y0 = rng() * H * 0.4, len = (0.3 + rng() * 0.6) * H; const x2 = x + (rng() - 0.5) * 40, y2 = y0 + len; lines += ``; } return ` ${lines}`; } /* ---- the golden sea: three sheets, EQUAL line count, φ-ratioed waves ---- */ function deck(mirror) { const base = { mode: 'plate', width: W, height: H, horizon: HORIZON_F, wFar: 0.5, wNear: 0.5 * PHI, // near:far width = φ overlap: PHI, // wave amplitude : row gap = φ rows: 48, mound: 0.4, sat: 0.58, lightNear: 0.33, lightFar: 0.56, chaos: 0.82, blips: 1.5, }; const lerp = (a, b, t) => a + (b - a) * t; const strokes = [{ near: 2.6, far: 1.0 }, { near: 1.6, far: 0.6 }, { near: 1.0, far: 0.38 }]; return [0, 1, 2].map(i => { const t = i / 2; return carpetSVG(H, { ...base, salt: (mirror ? 'mfield' : 'field') + i, hue: lerp(0.55, 0.47, t), hue2: lerp(0.55, 0.47, t) + 0.035, chaos: lerp(0.82 * 0.8, 0.82, t), strokeNear: strokes[i].near * 1.35, strokeFar: strokes[i].far * 1.35 }); }); } /* ---- the event: golden-placed disk + traces ---- */ function bcAssembleParams(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; Object.assign(p, { invert: true, transparentPaper: true, vign: 0, showHeader: false, showBoundary: false, showFiducials: false, annotate: 0, reseau: 0, filmEdge: false, splice: false, artifacts: 0, palette: 'magbeta', saturation: 1.0, diskBubbles: false, diskPressure: 0.9, diskSoften: 1.4, shockStriations: 1.0, diskHollow: 1.0, depth: 0.3, aging: 0.28, // event + shock both at scene origin; each is PLACED on the page by origin eventX: 0, eventY: 0, shockX: 0, shockY: 0, sweepers: 5, primaries: 17, eloss: 0.34, deltaRate: 0.8, bgEvents: 2, shockSize: SUN_R / S_SCALE, // radius = 0.382 of the sky band shockIntensity: 0.8, size: 1.6, // larger bubbles canvasW: W, canvasH: H, }, over, { seed }); return p; } const TRACE_ZOOM = 2.1; // the evidence grows past the frame function renderVariant(v, i) { const sun = v.mirror ? { x: XR, y: SUN.y } : SUN; const vtx = v.mirror ? { x: XL, y: HY } : VERTEX; const p = bcAssembleParams(`${SEED}#${String(i).padStart(2, '0')}`); const scene = generateScene(p); const bare = { ...scene, instrument: null, artifacts: null, media: null }; const tracksImg = dataUri(renderSVG( { ...bare, tracks: scene.tracks, shock: null }, { ...p, emit: ['bubble'], originX: vtx.x, originY: vtx.y, sceneZoom: TRACE_ZOOM }, W)); const diskImg = dataUri(renderSVG( { ...bare, tracks: [], shock: scene.shock }, { ...p, emit: ['disk'], originX: sun.x, originY: sun.y }, W)); const sheets = deck(v.mirror).map(dataUri); const film = dataUri(filmSVG({ seed: 41, density: 0.6 })); const grain = dataUri(grainSVG({ amount: 0.52 })); const aging = dataUri(agingSVG({ seed: 5 + i })); const hpx = HY.toFixed(1); const IMG = (href, attrs = '') => ``; // three sea planes stepped in opacity by 1/φ (front → back) const OP = [0.95 * IP2, 0.95 * IP, 0.95]; const svg = ` ${IMG(film, 'opacity="0.6"')} ${IMG(diskImg, 'filter="url(#diskback)" opacity="0.5"')} ${IMG(sheets[0], `filter="url(#b3)" opacity="${OP[0].toFixed(2)}"`)} ${IMG(diskImg, 'filter="url(#subD)" opacity="0.85"')} ${IMG(sheets[1], `filter="url(#b2)" opacity="${OP[1].toFixed(2)}"`)} ${IMG(sheets[2], `opacity="${OP[2].toFixed(2)}"`)} ${IMG(tracksImg)} ${IMG(aging, 'opacity="0.5" style="mix-blend-mode:multiply"')} ${IMG(grain, 'opacity="0.45" style="mix-blend-mode:multiply"')} `; writeFileSync(`${OUT}/${v.name}.svg`, svg); console.log(` ${v.name} sun(${sun.x.toFixed(0)},${sun.y.toFixed(0)}) vtx(${vtx.x.toFixed(0)},${vtx.y.toFixed(0)}) ${v.mirror ? 'mirrored' : ''}`); } const VARIANTS = [ { name: '00_golden', mirror: false }, { name: '01_golden', mirror: false }, { name: '02_golden', mirror: false }, { name: '03_golden-mirror', mirror: true }, { name: '04_golden-mirror', mirror: true }, { name: '05_golden-mirror', mirror: true }, ]; console.log(`ONE SUN SETTING · GOLDEN — ${W}×${H} (φ) — ${VARIANTS.length} renders → ${OUT}/`); VARIANTS.forEach(renderVariant); writeFileSync(`${OUT}/index.html`, `REFINED_Backlit4 · golden

One sun setting — golden (${W}×${H}, φ)

${VARIANTS.map(v => `
${v.name}
`).join('')}
`); console.log(`gallery -> ${OUT}/index.html`);