/* ============================================================ liberation.mjs — severing the anchors. The sea need not sit at the bottom; the sun and event need not stay on-canvas; the horizon need not be level. Each composition breaks a default we'd quietly started treating as a law. Built through the schema (proving it affords this). Usage: node tools/liberation.mjs [size] ============================================================ */ import { writeFileSync, mkdirSync } from 'node:fs'; import { DEFAULT_COMPOSITION } from '../src/compose/schema.js'; import { renderComposition } from '../src/compose/composition.js'; const SIZE = +(process.argv[2] || 1300); const OUT = 'output/liberation'; mkdirSync(OUT, { recursive: true }); 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 C = (seed, over) => { const c = merge(JSON.parse(JSON.stringify(DEFAULT_COMPOSITION)), over); c.seed = seed; return c; }; const V = [ // sea off the bottom ------------------------------------------------------ { name: '01_sea-on-top', note: 'sea hung from the top (rotated 180°)', comp: C('INVERT-7741', { fieldSea: { transform: { rotation: 180 } }, disk: { transform: { x: 0, y: 0.4, scale: 0.7 } }, bubble: { transform: { x: 0.28, y: 0.46 } }, fiduciaries: { target: [0.28, 0.46] } }) }, { name: '02_sea-vertical-stripe', note: 'sea as a vertical stripe down the middle', comp: C('COLUMN-2208', { fieldSea: { transform: { rotation: 90, scale: 1.0 }, clip: [0.37, 0, 0.63, 1] }, fieldGrid: { enabled: false }, disk: { transform: { x: -0.55, y: -0.2, scale: 0.55 } }, bubble: { transform: { x: 0.55, y: 0.2, scale: 0.55 } } }) }, { name: '03_mid-band-sea', note: 'sea confined to a band across the vertical middle', comp: C('STRATA-0613', { fieldSea: { transform: { y: -0.32 }, clip: [0, 0.36, 1, 0.64], horizonY: 0.2 }, fieldGrid: { enabled: false }, disk: { transform: { x: -0.1, y: -0.55, scale: 0.5 } }, bubble: { transform: { x: 0.2, y: 0.55, scale: 0.5 } } }) }, { name: '04_tilted-horizon', note: 'the horizon listing 8° — a world off true', comp: C('LISTING-1900', { fieldSea: { transform: { rotation: 8 } }, fieldGrid: { transform: { rotation: 8 } }, disk: { transform: { x: -0.1, y: -0.22 } } }) }, { name: '05_corner-sea', note: 'sea as a patch in one corner, the rest void', comp: C('QUADRANT-44', { fieldSea: { transform: { scale: 0.62, x: -0.42, y: -0.42 }, clip: [0, 0, 0.58, 0.58] }, fieldGrid: { enabled: false }, disk: { transform: { x: 0.4, y: 0.3, scale: 0.55 } }, bubble: { transform: { x: 0.5, y: -0.4, scale: 0.5 } } }) }, // sun / event off-canvas -------------------------------------------------- { name: '06_moon-cropped-top', note: 'the sun cropped by the top edge — a rising disk', comp: C('LUNA-3380', { disk: { transform: { x: 0.12, y: -1.02, scale: 1.4 }, pressure: 0.9 }, bubble: { transform: { x: -0.1, y: 0.2, scale: 0.6 } }, fiduciaries: { target: [-0.1, 0.2] } }) }, { name: '07_event-off-corner', note: 'the collision bleeding off the lower-right corner', comp: C('SPILL-9052', { bubble: { transform: { x: 1.08, y: 0.95, rotation: 0, scale: 1.15 } }, disk: { transform: { x: -0.4, y: -0.35, scale: 0.5 } }, fiduciaries: { target: [0.8, 0.7], from: [-0.2, -0.2] } }) }, { name: '08_eclipse-overfill', note: 'the sun overfilling the frame — only the rim on-canvas', comp: C('ECLIPSE-0001', { disk: { transform: { x: 0.05, y: 0.0, scale: 2.4 }, pressure: 0.95, hue: 0.0 }, bubble: { transform: { x: 0.7, y: -0.55, scale: 0.4 } }, fieldSea: { layerOpacity: 0.5 }, fiduciaries: { target: [0.7, -0.55], from: [0.2, -0.5] } }) }, // hierarchy / ground severed --------------------------------------------- { name: '09_dominant-grid', note: 'the grid is the subject; the sea a faint accent', comp: C('LATTICE-8810', { fieldGrid: { opacity: 0.62, transform: { scale: 1.5 }, layerOpacity: 1 }, fieldSea: { layerOpacity: 0.4 }, disk: { transform: { scale: 0.55, y: -0.3 } } }) }, { name: '10_crossing-fields', note: 'sea horizontal, grid rotated vertical — a woven space', comp: C('WEAVE-1221', { fieldGrid: { transform: { rotation: 90 }, opacity: 0.42, pos: 'over' }, disk: { transform: { x: -0.15, y: -0.2, scale: 0.6 } }, bubble: { transform: { x: 0.3, y: 0.25 } } }) }, { name: '11_night-plate', note: 'inverted ground — a dark deep-sky plate', comp: C('NOCTURNE-2330', { background: { color: 'rgb(20,20,28)', glow: { strength: 0.35, followSun: true }, vignette: { strength: 0.2 }, film: { opacity: 0.3 }, grain: { opacity: 0.3 } }, fieldSea: { color: { hueBack: 0.58, hueFront: 0.54, sat: 0.4 } }, disk: { hue: 0.08, pressure: 0.4, intensity: 0.95 } }) }, { name: '12_double-horizon', note: 'two horizons — a high grid line over the sea', comp: C('STRATUM-7007', { fieldGrid: { originY: -0.22, pitch: 0.22, opacity: 0.4, pos: 'over' }, fieldSea: { horizonY: 0.58 }, disk: { transform: { x: 0.0, y: -0.62, scale: 0.45 } }, bubble: { transform: { x: 0.25, y: 0.2 } } }) }, // pure void / extreme placement ------------------------------------------ { name: '13_vast-void', note: 'everything banished to the edges; a held emptiness', comp: C('EMPTY-0000', { fieldSea: { transform: { rotation: 180, y: 0.0 }, clip: [0, 0, 1, 0.16], layerOpacity: 0.7 }, disk: { transform: { x: -0.92, y: -0.78, scale: 0.34 } }, bubble: { transform: { x: 0.9, y: 0.85, scale: 0.34 } }, fieldGrid: { enabled: false }, fiduciaries: { target: [0.9, 0.85], from: [0.4, 0.6] } }) }, { name: '14_diagonal-world', note: 'the whole field canted into a diagonal sweep', comp: C('CANT-1789', { fieldSea: { transform: { rotation: 22 } }, fieldGrid: { transform: { rotation: 22, scale: 1.2 }, yaw: 0.6, opacity: 0.4 }, disk: { transform: { x: -0.35, y: -0.35, scale: 0.55 } }, bubble: { transform: { x: 0.35, y: 0.35, rotation: 28, scale: 0.6 } } }) }, ]; console.log(`liberation — ${V.length} compositions → ${OUT}/`); for (const v of V) { writeFileSync(`${OUT}/${v.name}.svg`, renderComposition(v.comp, SIZE)); console.log(` ${v.name} — ${v.note}`); } writeFileSync(`${OUT}/m.html`, `Liberation — severing the anchors

Liberation — severing the anchors (${V.length})

${V.map(v => `
${v.note}
`).join('')}
`); console.log(`-> ${OUT}/m.html`);