125 lines
7.8 KiB
JavaScript
125 lines
7.8 KiB
JavaScript
|
|
/* ============================================================
|
|||
|
|
dunelake.mjs — DUNES ON A GREAT LAKE WITH MILKWEED.
|
|||
|
|
First plate of the hidden-realities series: one invisible wind
|
|||
|
|
field made visible by three detectors — dunes (its time
|
|||
|
|
integral, what the sand remembers), water (its instantaneous
|
|||
|
|
trace), milkweed seeds (its advection). The umbel is a particle
|
|||
|
|
vertex grown botanically.
|
|||
|
|
|
|||
|
|
A curated variation matrix in the liberation.mjs manner:
|
|||
|
|
worlds (seed × wind character) × horizons × milkweed states ×
|
|||
|
|
palettes. Emits SVGs + exportable .mjs compositions + gallery.
|
|||
|
|
Usage: node tools/dunelake.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/dunelake';
|
|||
|
|
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; };
|
|||
|
|
|
|||
|
|
/* The landscape base: no collision event, no sun — the subjects are the
|
|||
|
|
field's three detectors. horizon = waterline (frame fraction). */
|
|||
|
|
const base = (seed, windSeed, windStrength, horizon, over = {}) => {
|
|||
|
|
const hClip = horizon; // lake band ends where dunes begin
|
|||
|
|
const c = merge(JSON.parse(JSON.stringify(DEFAULT_COMPOSITION)), merge({
|
|||
|
|
seed,
|
|||
|
|
wind: { seed: windSeed, strength: windStrength, scale: 1.6, gust: 0.4, time: 1.4 },
|
|||
|
|
background: {
|
|||
|
|
color: 'rgb(226,216,192)',
|
|||
|
|
glow: { strength: 0.4, followSun: false },
|
|||
|
|
vignette: { strength: 0.32, mode: 'radial', cy: 0.42, radius: 0.9 },
|
|||
|
|
film: { opacity: 0.5 }, aging: { opacity: 0.35, scratches: 3 }, grain: { opacity: 0.38 },
|
|||
|
|
},
|
|||
|
|
// WATER: a flat far band, instantaneous wind, cool — clipped above the dune line
|
|||
|
|
fieldLake: {
|
|||
|
|
enabled: true, seed: 'LIMNOS-' + seed,
|
|||
|
|
color: { hueBack: 0.555, hueFront: 0.52, sat: 0.42, light: 0.52 },
|
|||
|
|
layers: 2, chaos: 0.1, blips: 0.2, mound: 0, lines: 24,
|
|||
|
|
horizonY: Math.max(0.2, horizon - 0.16), wind: { warp: 0.55 },
|
|||
|
|
clip: [0, Math.max(0, horizon - 0.2), 1, horizon + 0.02],
|
|||
|
|
},
|
|||
|
|
// DUNES: the ridgeline carpet warped by the wind's time integral, warm sand
|
|||
|
|
fieldSea: {
|
|||
|
|
seed: 'AEOLIAN-' + seed,
|
|||
|
|
color: { hueBack: 0.09, hueFront: 0.07, sat: 0.42, light: 0.42 },
|
|||
|
|
layers: 3, chaos: 0.22, blips: 0.35, mound: 0.55, horizonY: horizon, lines: 52,
|
|||
|
|
wind: { warp: 0.9 },
|
|||
|
|
},
|
|||
|
|
fieldGrid: { enabled: false },
|
|||
|
|
disk: { enabled: false },
|
|||
|
|
bubble: { enabled: false },
|
|||
|
|
// MILKWEED: vertex-flowers on the foredune
|
|||
|
|
flora: {
|
|||
|
|
enabled: true, seed: 'ASCLEPIAS-' + seed, palette: 'dunelake',
|
|||
|
|
count: 3, scale: 0.42, pedicels: 46, droop: 0.4, burst: 0.25,
|
|||
|
|
clusterW: 0.7, clusterH: 0.3,
|
|||
|
|
transform: { x: -0.34, y: 0.42, rotation: 0, scale: 1 },
|
|||
|
|
},
|
|||
|
|
drift: { enabled: true, count: 28, slip: 0.4, flutter: 0.55, tuft: 2 },
|
|||
|
|
fiduciaries: { enabled: true, label: 'ASCLEPIAS SYRIACA', arrow: true, target: [-0.34, 0.42], from: [0.35, -0.3], caption: 'wind · integrated | instantaneous | advected' },
|
|||
|
|
}, over));
|
|||
|
|
return c;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// dusk: warm-on-cool — sand holds the late light, the water goes slate
|
|||
|
|
const dusk = {
|
|||
|
|
background: { color: 'rgb(210,190,170)', glow: { strength: 0.5 } },
|
|||
|
|
fieldSea: { color: { hueBack: 0.05, hueFront: 0.95, sat: 0.5, light: 0.4 } },
|
|||
|
|
fieldLake: { color: { hueBack: 0.62, hueFront: 0.58, sat: 0.5, light: 0.42 } },
|
|||
|
|
};
|
|||
|
|
// nocturne: the night-plate move — dark ground, pale silks (liberation #11)
|
|||
|
|
const nocturne = {
|
|||
|
|
background: { color: 'rgb(22,23,30)', glow: { strength: 0.3, followSun: false }, vignette: { strength: 0.2 }, film: { opacity: 0.28 }, grain: { opacity: 0.3 } },
|
|||
|
|
fieldSea: { color: { hueBack: 0.6, hueFront: 0.08, sat: 0.3, light: 0.62 } },
|
|||
|
|
fieldLake: { color: { hueBack: 0.58, hueFront: 0.56, sat: 0.4, light: 0.66 } },
|
|||
|
|
fiduciaries: { pencil: '#cfc4a8' },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const W = [
|
|||
|
|
{ id: 'breeze', seed: 'ZEPHYR-011', strength: 0.55 },
|
|||
|
|
{ id: 'gale', seed: 'BOREAS-104', strength: 1.25 },
|
|||
|
|
];
|
|||
|
|
const SEEDS = ['LAKEMICH-0610', 'SLEEPING-BEAR-22', 'CRITICALDUNE-7'];
|
|||
|
|
|
|||
|
|
const V = [];
|
|||
|
|
for (const s of SEEDS) {
|
|||
|
|
for (const w of W) {
|
|||
|
|
const tag = `${s.split('-')[0].toLowerCase()}-${w.id}`;
|
|||
|
|
// big-sky vs big-water horizons
|
|||
|
|
V.push({ name: `${tag}_low-horizon`, note: `${w.id} · big sky, dunes low (${s})`,
|
|||
|
|
comp: base(s, w.seed, w.strength, 0.62) });
|
|||
|
|
V.push({ name: `${tag}_high-water`, note: `${w.id} · big water, high dune line (${s})`,
|
|||
|
|
comp: base(s, w.seed, w.strength, 0.44, { fieldSea: { lines: 44 }, flora: { transform: { x: -0.3, y: 0.5 } }, fiduciaries: { target: [-0.3, 0.5] } }) });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// milkweed states — closed / bursting / full release streaming across the lake
|
|||
|
|
const sBurst = SEEDS[0], sFull = SEEDS[1];
|
|||
|
|
V.push({ name: 'states_closed-left', note: 'umbels closed — the vertex before the decay',
|
|||
|
|
comp: base(sBurst, W[0].seed, W[0].strength, 0.56, { flora: { burst: 0, count: 2, transform: { x: -0.5, y: 0.38 } }, drift: { enabled: false }, fiduciaries: { target: [-0.5, 0.38], caption: 'pre-release · all florets held' } }) });
|
|||
|
|
V.push({ name: 'states_bursting-right', note: 'right-third bursting — half the florets gone to silk',
|
|||
|
|
comp: base(sBurst, W[1].seed, W[1].strength, 0.56, { flora: { burst: 0.55, transform: { x: 0.42, y: 0.44 } }, drift: { count: 40 }, fiduciaries: { target: [0.42, 0.44], caption: 'release fraction 0.55' } }) });
|
|||
|
|
V.push({ name: 'states_full-release', note: 'off-corner full burst — seeds streaming across the water',
|
|||
|
|
comp: base(sFull, W[1].seed, 1.4, 0.5, { flora: { burst: 1, count: 2, transform: { x: -0.78, y: 0.62 }, scale: 0.5 }, drift: { count: 64, slip: 0.5 }, fiduciaries: { target: [-0.78, 0.62], from: [0.4, -0.35], caption: 'all silk · the field made visible' } }) });
|
|||
|
|
|
|||
|
|
// palette rows over one world: day-sand (the base), dusk, nocturne
|
|||
|
|
V.push({ name: 'palette_dusk', note: 'dusk — sand holds the late light, slate water',
|
|||
|
|
comp: base(SEEDS[2], W[0].seed, 0.8, 0.55, dusk) });
|
|||
|
|
V.push({ name: 'palette_nocturne', note: 'nocturne — dark plate, pale silks (night-plate move)',
|
|||
|
|
comp: base(SEEDS[2], W[1].seed, 1.1, 0.55, merge(nocturne, { flora: { burst: 0.6 }, drift: { count: 48 } })) });
|
|||
|
|
|
|||
|
|
console.log(`dunelake — ${V.length} compositions → ${OUT}/`);
|
|||
|
|
for (const v of V) {
|
|||
|
|
writeFileSync(`${OUT}/${v.name}.svg`, renderComposition(v.comp, SIZE));
|
|||
|
|
writeFileSync(`${OUT}/${v.name}.mjs`, `/* dune-lake-milkweed · ${v.note} */\nexport const composition = ${JSON.stringify(v.comp, null, 2)};\n`);
|
|||
|
|
console.log(` ${v.name} — ${v.note}`);
|
|||
|
|
}
|
|||
|
|
writeFileSync(`${OUT}/matrix.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Dunes on a Great Lake — one wind, three detectors</title>
|
|||
|
|
<style>html,body{margin:0;background:#15140f;color:#cabfa6;font:12px ui-monospace,monospace}h1{font-weight:400;letter-spacing:.2em;text-transform:uppercase;color:#9fb7af;padding:18px 14px 0}p.sub{color:#8a8068;padding:0 14px;margin:6px 0 0}.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:14px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 9px;font:12px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|||
|
|
<h1>Dunes on a Great Lake with Milkweed (${V.length})</h1>
|
|||
|
|
<p class="sub">one invisible wind field · three detectors — sand (integrated) · water (instantaneous) · seeds (advected)</p>
|
|||
|
|
<div class="grid">${V.map(v => `<figure><img src="${v.name}.svg"><figcaption>${v.note}</figcaption></figure>`).join('')}</div></body></html>`);
|
|||
|
|
console.log(`-> ${OUT}/matrix.html`);
|