vast ocean

This commit is contained in:
2026-05-30 06:42:16 -04:00
parent e38f11f71a
commit 52453fba67
68 changed files with 1704 additions and 66 deletions

View File

@@ -128,9 +128,12 @@ export function carpetSVG(size, opts = {}) {
body += `<path d="${path}" fill="none" stroke="${col}" stroke-width="${sw.toFixed(2)}" stroke-opacity="${op}" stroke-linecap="round" stroke-linejoin="round"/>\n`;
}
const bg = transparent ? '' : `<rect width="${W}" height="${H}" fill="${css(paper)}"/>`;
// orient: 'vertical' rotates the whole field 90° → lines run vertically and
// recede to a vanishing edge (a curtain / rays of vacuum strings).
const inner = o.orient === 'vertical' ? `<g transform="rotate(-90 ${(W / 2)} ${(H / 2)})">${body}</g>` : body;
return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
${bg}
<g>${body}</g>
<g>${inner}</g>
</svg>`;
}

View File

@@ -44,6 +44,9 @@ export function renderSVG(scene, params, sizePx = 4800) {
const paperRGB = pal.paper(); // {flat,glowIn,glowOut} rgb arrays
const paperC = { flat: rgbHex(paperRGB.flat), glowIn: rgbHex(paperRGB.glowIn), glowOut: rgbHex(paperRGB.glowOut) };
const ink = rgbHex(pal.feature()); // non-particle marks (optics, disk, damage, header)
// diskPressure: a darker, denser core (compressed gas = higher pressure)
const press = Math.max(0, Math.min(1, params.diskPressure ?? 0));
const coreInk = rgbHex(mix(pal.feature(), [0, 0, 0], press * 0.7));
const featKey = rgbKey(pal.feature());
const colorMap = new Map([[featKey, pal.feature()]]); // distinct bubble colours → gradients
@@ -52,7 +55,9 @@ export function renderSVG(scene, params, sizePx = 4800) {
content ? `<g id="${id}" inkscape:groupmode="layer" inkscape:label="${attr(label)}" style="display:inline"${extra ? ' ' + extra : ''}>\n${content}\n</g>\n` : '';
/* ---------- Background ---------- */
const bg = `<rect width="${w}" height="${h}" fill="${paperC.flat}"/>\n<rect width="${w}" height="${h}" fill="url(#paper)"/>`;
// transparentPaper: omit paper + vignette so the event can float as an object
// over another layer (e.g. the QFT carpet) without a paper rectangle.
const bg = params.transparentPaper ? '' : `<rect width="${w}" height="${h}" fill="${paperC.flat}"/>\n<rect width="${w}" height="${h}" fill="url(#paper)"/>`;
/* ---------- Chamber optics ---------- */
let optics = '';
@@ -206,7 +211,7 @@ export function renderSVG(scene, params, sizePx = 4800) {
}
/* ---------- Vignette ---------- */
const vign = params.vign > 0 ? `<rect width="${w}" height="${h}" fill="url(#vign)"/>` : '';
const vign = (params.vign > 0 && !params.transparentPaper) ? `<rect width="${w}" height="${h}" fill="url(#vign)"/>` : '';
/* ---------- Header ---------- */
let header = '';
@@ -258,7 +263,8 @@ export function renderSVG(scene, params, sizePx = 4800) {
media += `</g>`;
}
if (M.grease && M.grease.length) {
const ch = inv ? '#9c1e1e' : '#f0e296';
// annotateInk overrides the chinagraph colour (e.g. white pencil)
const ch = params.annotateInk || (inv ? '#9c1e1e' : '#f0e296');
let g = `<g stroke="${ch}" fill="${ch}" stroke-linecap="round" stroke-linejoin="round">`;
for (const gm of M.grease) {
if (gm.kind === 'ring' || gm.kind === 'arrow') {
@@ -288,7 +294,7 @@ export function renderSVG(scene, params, sizePx = 4800) {
let s = `<?xml version="1.0" encoding="UTF-8"?>\n`;
s += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">\n`;
s += `<metadata>Bubble Chamber · seed=${params.seed} · hash=${cyrb53(params.seed)} · palette=${params.palette || 'mono'}</metadata>\n`;
s += defs({ paperC, ink, baseVign: pal.vign(), params, u, colorMap });
s += defs({ paperC, ink, coreInk, press, baseVign: pal.vign(), params, u, colorMap });
s += layer('background', 'Background', bg);
s += layer('optics', 'Chamber optics', optics);
s += layer('shock', 'Shock disk', shock, params.diskSoften > 0 ? 'filter="url(#soften)"' : '');
@@ -302,7 +308,7 @@ export function renderSVG(scene, params, sizePx = 4800) {
return s;
}
function defs({ paperC, ink, baseVign, params, u, colorMap }) {
function defs({ paperC, ink, coreInk, press = 0, baseVign, params, u, colorMap }) {
const soften = params.diskSoften > 0
? `<filter id="soften" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="${(params.diskSoften * u).toFixed(2)}"/></filter>`
: '';
@@ -324,8 +330,9 @@ function defs({ paperC, ink, baseVign, params, u, colorMap }) {
</radialGradient>
${bubGrads}
<radialGradient id="shockcore" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="${ink}" stop-opacity="0.5"/>
<stop offset="60%" stop-color="${ink}" stop-opacity="0.28"/>
<stop offset="0%" stop-color="${coreInk || ink}" stop-opacity="${(0.5 + press * 0.45).toFixed(2)}"/>
<stop offset="28%" stop-color="${coreInk || ink}" stop-opacity="${(0.32 + press * 0.32).toFixed(2)}"/>
<stop offset="62%" stop-color="${ink}" stop-opacity="0.28"/>
<stop offset="100%" stop-color="${ink}" stop-opacity="0"/>
</radialGradient>
<radialGradient id="shockstain" cx="50%" cy="50%" r="50%">

View File

@@ -42,6 +42,7 @@ export function generateMedia(params, scene, rng) {
/* ---- grease-pencil (chinagraph) hand marks ---- */
if ((params.annotate ?? 0) > 0) {
const full = params.annotate >= 0.66; // restrained vs studied
const aw = params.annoWidth ?? 1; // line-weight multiplier (thinner = <1)
const v = scene.vertex || { x: 0, y: 0 };
// ring a δ-ray curl if there is one, else the vertex region
const deltas = scene.tracks.filter(t => t.kind === 'delta' && t.pts.length > 6);
@@ -50,19 +51,22 @@ export function generateMedia(params, scene, rng) {
const d = pick(rng, deltas), p = d.pts[Math.floor(d.pts.length * 0.4)];
cx = p.x; cy = p.y; cr = 0.05 + rng() * 0.035;
} else { cx = v.x + gauss(rng) * 0.05; cy = v.y + gauss(rng) * 0.05; cr = 0.11 + rng() * 0.05; }
out.grease.push({ kind: 'ring', pts: wobblyRing(cx, cy, cr, rng), width: 2.4 });
if (params.annoRing !== false) out.grease.push({ kind: 'ring', pts: wobblyRing(cx, cy, cr, rng), width: 2.4 * aw });
// arrow from open space toward the ring
const side = cx <= 0 ? 1 : -1;
const ax = cx + side * (0.34 + rng() * 0.12), ay = cy - (0.30 + rng() * 0.12);
const tipx = cx - side * cr * 1.25, tipy = cy - cr * 1.15;
const ang = Math.atan2(tipy - ay, tipx - ax);
out.grease.push({ kind: 'arrow', shaft: wobblyLine(ax, ay, tipx, tipy, rng), tip: { x: tipx, y: tipy, ang }, width: 2.0 });
// arrow from open space toward the ring/find
if (params.annoArrow !== false) {
const side = cx <= 0 ? 1 : -1;
const ax = cx + side * (0.34 + rng() * 0.12), ay = cy - (0.30 + rng() * 0.12);
const tipx = cx - side * cr * 1.25, tipy = cy - cr * 1.15;
const ang = Math.atan2(tipy - ay, tipx - ax);
out.grease.push({ kind: 'arrow', shaft: wobblyLine(ax, ay, tipx, tipy, rng), tip: { x: tipx, y: tipy, ang }, width: 2.0 * aw });
}
// event number by the ring
out.grease.push({ kind: 'text', x: cx + cr * 1.05, y: cy - cr * 1.05, text: `${scene.plate}`, size: 0.045, rot: -0.05 + gauss(rng) * 0.03 });
// event number by the ring/find
if (params.annoNum !== false)
out.grease.push({ kind: 'text', x: cx + cr * 1.05, y: cy - cr * 1.05, text: params.annoLabel ?? `${scene.plate}`, size: 0.045, rot: -0.05 + gauss(rng) * 0.03 });
if (full) {
if (full && params.annoExtras !== false) {
// angle arc + reading at the vertex
const a0 = rng() * TWO_PI, a1 = a0 + (0.5 + rng() * 0.6);
out.grease.push({ kind: 'arc', x: v.x, y: v.y, r: 0.12 + rng() * 0.05, a0, a1, width: 1.6 });

View File

@@ -96,8 +96,11 @@ export function generateScene(params) {
const rng = makeRng(params.seed, 'scene');
const tracks = [];
// Foreground event, slightly off-centre — near the focal plane, current trigger
const fgVertex = { x: (rng() - 0.5) * 0.3, y: (rng() - 0.5) * 0.3 };
// Foreground event, slightly off-centre — near the focal plane, current trigger.
// eventX/eventY (in [-1,1] canvas coords) override the seed placement for
// deliberate composition; rng() is still consumed so the rest stays deterministic.
const rvx = (rng() - 0.5) * 0.3, rvy = (rng() - 0.5) * 0.3;
const fgVertex = { x: params.eventX ?? rvx, y: params.eventY ?? rvy };
const fgZ = gauss(rng) * 0.12;
tracks.push(...generateOneEvent(params, fgVertex, 1.0, 'fg', fgZ, 0));