/* ============================================================ proof1-render.mjs — PROOF 1: the lit chamber, split into planes. Takes the locked golden merge composition (REFINED_Backlit7, the mirror event, uniform-lace merge) and emits it as SEPARATE transparent planes for a backlit acrylic stack, plus a manifest and a parallax/backlight simulator to art-direct the depth BEFORE committing to print. Plane map (deepest → nearest), causal depth cause→world→evidence: 1 sun faded disk · pure transmitted colour · zero white 2 sea-far sheet[0] 3 sea-mid sheet[1] 4 sea-near sheet[2] 5 event-halo merged bubbles, blurred by the air gap (glow) 6 event-core merged bubbles, sharp · white underlay + front-glass grain + aging (the surface, multiply) Golden frame (φ) is preserved and FLOATED in an A3 sheet later; this stage renders the art square only. Usage: node tools/proof1-render.mjs [width] → fable/Proof1/ ============================================================ */ 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'; const PHI = (1 + Math.sqrt(5)) / 2; const IP = 1 / PHI, IP2 = 1 / (PHI * PHI); const W = +(process.argv[2] || 1001); const H = Math.round(W * PHI); // golden portrait const OUT = 'fable/Proof1'; mkdirSync(OUT, { recursive: true }); const u = Math.min(W, H) / 1000; const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64'); /* ---- golden armature (identical to REFINED_Backlit7) ---- */ const SEED = 'MESON-5113'; const SEED_EVENT = `${SEED}#03`; // the chosen mirror event const BUBOP = +(process.env.BUBOP ?? 0.5); // mid of the 45→55 band const HORIZON_F = IP2; const HY = HORIZON_F * H, SKY_H = HY; const S_SCALE = (Math.min(W, H) / 2) * (1 - 0.02); const XL = IP2 * W, XR = IP * W; const SUN = { x: XR, y: IP * SKY_H }; // mirror: sun on the RIGHT line const SUN_R = IP2 * SKY_H; const VERTEX = { x: XL, y: HY }; // mirror: vertex on the LEFT line const TRACE_ZOOM = 2.1; /* ---- textures ---- */ 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)); let lines = ''; // deterministic scratch scatter without Math.random (seeded LCG) let s = seed * 2654435761 >>> 0; const rnd = () => ((s = (s * 1664525 + 1013904223) >>> 0) / 4294967296); for (let i = 0; i < scratches; i++) { const x = rnd() * W, y0 = rnd() * H * 0.4, len = (0.3 + rnd() * 0.6) * H; const x2 = x + (rnd() - 0.5) * 40, y2 = y0 + len; lines += ``; } return ` ${lines}`; } /* ---- golden sea deck (equal-line, φ waves) ---- */ function deck() { const base = { mode: 'plate', width: W, height: H, horizon: HORIZON_F, wFar: 0.5, wNear: 0.5 * PHI, overlap: PHI, 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: 'mfield' + 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 }); }); } /* ---- event params (merge, delicate, golden-placed) ---- */ function bcAssembleParams() { 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, 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, shockIntensity: 0.8, size: 1.6, bubbleHollow: 1, bubbleWeight: 0.7, bubbleSoft: 0.7, bubbleMerge: 1, bubbleOpacity: BUBOP, canvasW: W, canvasH: H, seed: SEED_EVENT, }); return p; } /* ---- render the source images ---- */ const p = bcAssembleParams(); const scene = generateScene(p); const bare = { ...scene, instrument: null, artifacts: null, media: null }; const tracksImg = renderSVG({ ...bare, tracks: scene.tracks, shock: null }, { ...p, emit: ['bubble'], originX: VERTEX.x, originY: VERTEX.y, sceneZoom: TRACE_ZOOM }, W); const diskImg = renderSVG({ ...bare, tracks: [], shock: scene.shock }, { ...p, emit: ['disk'], originX: SUN.x, originY: SUN.y }, W); const sheets = deck(); const grain = grainSVG({ amount: 0.52 }); const aging = agingSVG({ seed: 8 }); /* ---- write each plane as a transparent SVG (art square only) ---- */ const planeSVG = (inner) => `${inner}`; const IMG = (svg) => ``; // grain+aging combined onto one front-glass texture (multiply handled in sim) const frontGlass = planeSVG( `${IMG(aging)}${IMG(grain)}`); // DOF blur is tuned per MARK TYPE: thin wave-lines dissolve under blur far // faster than dense bubbles, so line-art planes get very little (parallax // offset carries their depth). Bubbles/glow tolerate more. const PLANES = [ { id: 'sun', file: 'plane-1-sun.svg', z: 0, blur: 1.4, dim: 0.95, white: 'none', glow: 1.0, svg: planeSVG(IMG(diskImg)) }, { id: 'sea-far', file: 'plane-2-sea-far.svg', z: 9, blur: 0.9, dim: 1.0, white: 'none', glow: 0.7, svg: planeSVG(IMG(sheets[0])) }, { id: 'sea-mid', file: 'plane-3-sea-mid.svg', z: 15, blur: 0.5, dim: 1.0, white: 'trace', glow: 0.7, svg: planeSVG(IMG(sheets[1])) }, { id: 'sea-near', file: 'plane-4-sea-near.svg', z: 18, blur: 0.2, dim: 1.0, white: 'trace', glow: 0.7, svg: planeSVG(IMG(sheets[2])) }, { id: 'event-halo', file: 'plane-5-event-halo.svg', z: 27, blur: 2.4, dim: 0.85, white: 'none', glow: 0.9, svg: planeSVG(IMG(tracksImg)) }, { id: 'event-core', file: 'plane-6-event-core.svg', z: 30, blur: 0.0, dim: 1.0, white: 'under', glow: 0.5, svg: planeSVG(IMG(tracksImg)) }, { id: 'front-glass',file: 'plane-7-front-glass.svg',z: 30, blur: 0.0, dim: 1.0, white: 'none', glow: 0.0, blend: 'multiply', svg: frontGlass }, ]; for (const pl of PLANES) writeFileSync(`${OUT}/${pl.file}`, pl.svg); const manifest = { piece: 'Proof1 — the lit chamber', source: 'REFINED_Backlit7 merge (mirror event, uniform-lace)', seed: SEED_EVENT, bubbleOpacity: BUBOP, art: { w: W, h: H, ratio: 'phi (golden portrait)' }, sheet: { w_mm: 330, h_mm: 420, note: 'A3 — golden art square floated, clear margins = lit void' }, gaps_mm: 'plane z is nominal; two scales: ~3mm within a sheet, ~9mm between', planes: PLANES.map(({ svg, ...rest }) => rest), }; writeFileSync(`${OUT}/manifest.json`, JSON.stringify(manifest, null, 2)); /* ---- the backlit parallax simulator ---- */ const planesJSON = JSON.stringify(PLANES.map(({ svg, ...r }) => r)); writeFileSync(`${OUT}/simulator.html`, ` Proof1 · lit chamber — backlit parallax

Proof1 · lit chamber

move mouse = parallax · drag sliders
`); console.log(`PROOF1 — ${W}×${H} (φ) — ${PLANES.length} planes → ${OUT}/`); for (const pl of PLANES) console.log(` ${pl.file.padEnd(26)} z=${pl.z}mm blur=${pl.blur} white=${pl.white}`); console.log(`manifest -> ${OUT}/manifest.json`); console.log(`simulator -> ${OUT}/simulator.html`);