/* ============================================================ perspective4.mjs — Field 2 reborn as a STRAIGHT perspective floor grid (one/two-point), through the grouped schema. Constructed space receding to a vanishing point, under the decoupled sun + collision. Usage: node tools/perspective4.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/perspective4'; mkdirSync(OUT, { recursive: true }); const 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', style: 'floor', transform: { x: 0, y: 0, rotation: 0, scale: 1 }, color: { hue: 0.56, hue2: 0.5, sat: 0.26, lightNear: 0.32, lightFar: 0.64 }, opacity: 0.4, pitch: 28 * D, yaw: 0, persp: 1.0, dist: 3.0, nx: 16, nz: 24, originY: 0.34, ripple: { amp: 0 }, layers: 2, blurPerLayer: [1.1, 0], }, disk: { enabled: true, transform: { x: 0, y: -0.26, rotation: 0, scale: 0.78 }, hue: 0.06, sat: 0.82, size: 0.16, pressure: 0.85 }, bubble: { enabled: true, transform: { x: 0.28, y: -0.1, rotation: 10, scale: 0.78 }, 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 V = [ // 1 — a floor receding to its OWN horizon, below the sea's: two horizons. mk('01_second-horizon', { fieldGrid: { originY: 0.46, pitch: 22 * D, nz: 28, opacity: 0.4 }, bubble: { transform: { x: 0.3, y: -0.08 } } }), // 2 — a two-point room: the event measured in built space. mk('02_two-point-room', { fieldGrid: { yaw: 24 * D, pitch: 26 * D, dist: 2.8, opacity: 0.44 }, fiduciaries: { corners: true } }), // 3 — the floor itself tilted (field-layer rotation) under a low sun. mk('03_tilted-floor', { fieldGrid: { transform: { rotation: 9, scale: 1.05 }, pitch: 30 * D, opacity: 0.42 }, disk: { transform: { x: -0.04, y: -0.24 } }, bubble: { transform: { x: 0.26, y: -0.06, rotation: -6 } } }), // 4 — ghost floor behind the sea: constructed space half-sunk. mk('04_ghost-floor-behind', { fieldGrid: { pos: 'behind', opacity: 0.36, pitch: 24 * D, originY: 0.4 } }), // 5 — steep tight tiles crowding toward a low vanishing point. mk('05_steep-tiles', { fieldGrid: { pitch: 46 * D, dist: 2.4, nx: 14, nz: 20, originY: 0.2, opacity: 0.44 }, disk: { transform: { x: 0, y: 0.05, scale: 0.7 } }, bubble: { transform: { x: 0.22, y: 0.16 } } }), // 6 — the floor breathes: a gentle ripple, still legibly a grid. mk('06_gentle-rippled-floor', { fieldGrid: { ripple: { amp: 0.55, freqI: 0.5, freqK: 0.35, phase: 0.4 }, pitch: 26 * D, opacity: 0.42 } }), // 7 — coordinate-system dwarfs the event: zoomed grid, tiny far ember. mk('07_zoomed-grid-tiny-event', { fieldGrid: { transform: { scale: 1.45, x: -0.08 }, pitch: 24 * D, dist: 2.6, opacity: 0.42 }, disk: { transform: { x: 0.44, y: -0.4, scale: 0.42 }, hue: 0.0 }, bubble: { transform: { x: 0.4, y: -0.3, scale: 0.42 } }, fiduciaries: { from: [0.1, -0.2] } }), // 8 — teal built-space, magenta event: cool/warm two-colour. mk('08_teal-grid-magenta', { fieldGrid: { color: { hue: 0.5, hue2: 0.55, sat: 0.34, lightNear: 0.34, lightFar: 0.62 }, opacity: 0.46, pitch: 26 * D, yaw: 14 * D } }), // 9 — two-point, vanishing off to the side; the event near the far VP. mk('09_two-point-shifted', { fieldGrid: { yaw: -28 * D, pitch: 22 * D, originX: 0.18, dist: 2.6, opacity: 0.44 }, disk: { transform: { x: -0.35, y: -0.22, scale: 0.62 } }, bubble: { transform: { x: 0.34, y: -0.04 } }, fiduciaries: { from: [-0.3, -0.34] } }), // 10 — the floor dissolving into the paper: a warm near-blank coordinate ghost. mk('10_dissolving-floor', { fieldGrid: { color: { hue: 0.1, hue2: 0.08, sat: 0.16, lightNear: 0.46, lightFar: 0.68 }, opacity: 0.3, pitch: 20 * D, nz: 30, originY: 0.42 }, disk: { hue: 0.04 } }), ]; console.log(`perspective4 — straight floor grid · ${V.length} schema comps → ${OUT}/`); for (const v of V) { 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`, `perspective4 · straight floor grid
${V.map(v => `
${cap(v)}
`).join('')}
`); console.log(`-> ${OUT}/m.html`);