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