/* ============================================================ qft-variations-craft.mjs — sketch 06. Each variation isolates ONE craft idea so the effect is readable on its own. Five blocks: A · Cubic origin plays — push origin off-page or into the viewer's space along the (isometric) Z axis B · Multi-rosettes — offset origins, varied scales C · Lighter-on-midtone — paper at MID tone so a brighter rosette can actually read as lighter-than-ground D · Plain white on light — bold white strokes on warm paper E · Schlegel plays — weight / origin / shifted axes (outerR · innerR · rot3D) ============================================================ */ import { writeFileSync, mkdirSync } from 'node:fs'; import { generateQFTScene } from '../src/qft/scene.js'; import { paramsFromSeed } from '../src/qft/params.js'; import { renderQFTSVG } from '../src/qft/renderer.js'; const BASE_SEED = 'FEYNMAN-7167'; const OUT_DIR = 'output/qft/sketch06'; const SIZE = 1800; mkdirSync(OUT_DIR, { recursive: true }); const F = (hueStart, hueEnd, saturation, lightness, opacity) => ({ hueStart, hueEnd, saturation, lightness, opacity }); const paper = (flat, glowInDelta = [18, 16, 14], glowOutDelta = [-25, -22, -20]) => ({ flat, glowIn: [flat[0] + glowInDelta[0], flat[1] + glowInDelta[1], flat[2] + glowInDelta[2]], glowOut: [flat[0] + glowOutDelta[0], flat[1] + glowOutDelta[1], flat[2] + glowOutDelta[2]], }); const WHITE = (op = 0.95) => F(0, 0, 0, 0.97, op); const variations = [ // ─────────────────────── BLOCK A · cubic origin plays ─────────────────────── { name: 'A1_cubic-origin-far-right', label: 'A1 · cubic origin pushed off-page right — orthogonal mesh streams in from the right edge', overrides: { paperOverride: paper([225, 215, 200]), vignOverride: [70, 60, 45], featureOverride: [60, 45, 30], stroke: 1.8, gradientMode: 'linear-x', cubicScale: 1.4, cubicOriginX: 1.45, cubicOriginY: 0, // mostly off-screen to the right schlegelScale: 0.65, distStrength: 0, // disable distortion when origin is offset e8Origins: [{ x: -0.45, y: 0, scale: 0.42 }], // counterweight rosette left linkCount: 4, fields: { cubic: F(0.55, 0.08, 0.65, 0.35, 0.78), schlegel: F(0.10, 0.08, 0.45, 0.40, 0.55), e8: F(0.10, 0.04, 0.65, 0.45, 0.88), links: F(0.05, 0.10, 0.70, 0.40, 0.55), }, }, }, { name: 'A2_cubic-zoom-toward-viewer', label: 'A2 · cubic zoomed in (Z toward viewer) — we are INSIDE the lattice', overrides: { paperOverride: paper([225, 215, 198]), vignOverride: [60, 50, 35], featureOverride: [70, 55, 30], stroke: 2.4, cubicScale: 3.2, // huge — fills well past the frame schlegelScale: 0, distStrength: 0, e8Origins: [{ x: 0.55, y: 0.55, scale: 0.32 }], // single small rosette one corner linkCount: 3, fields: { cubic: F(0.04, 0.13, 0.65, 0.32, 0.80), schlegel: F(0, 0, 0, 0.5, 0), e8: F(0.05, 0.12, 0.65, 0.45, 0.88), links: F(0.06, 0.12, 0.65, 0.40, 0.55), }, }, }, { name: 'A3_cubic-origin-upper-left', label: 'A3 · cubic origin pushed upper-left, rest of canvas opens to lower-right', overrides: { paperOverride: paper([232, 222, 205]), vignOverride: [70, 55, 40], featureOverride: [60, 50, 35], stroke: 1.6, gradientMode: 'radial', cubicScale: 1.6, cubicOriginX: -0.85, cubicOriginY: -0.85, schlegelScale: 0.55, schlegelOriginX: -0.30, schlegelOriginY: -0.30, distStrength: 0, e8Origins: [{ x: 0.40, y: 0.40, scale: 0.45 }], // rosette in opposing corner linkCount: 4, fields: { cubic: F(0.55, 0.05, 0.65, 0.36, 0.80), schlegel: F(0.58, 0.10, 0.45, 0.40, 0.60), e8: F(0.06, 0.12, 0.70, 0.45, 0.88), links: F(0.05, 0.10, 0.65, 0.40, 0.55), }, }, }, // ─────────────────────── BLOCK B · multi-rosettes ──────────────────────────── { name: 'B1_three-rosettes-horizontal', label: 'B1 · three rosettes along a horizontal axis', overrides: { paperOverride: paper([232, 222, 205]), vignOverride: [60, 50, 35], featureOverride: [55, 45, 30], stroke: 1.8, cubicScale: 1.3, schlegelScale: 0, e8Origins: [ { x: -0.55, y: 0, scale: 0.32 }, { x: 0.00, y: 0, scale: 0.42 }, { x: 0.55, y: 0, scale: 0.32 }, ], linkCount: 6, fields: { cubic: F(0.50, 0.10, 0.45, 0.40, 0.35), schlegel: F(0, 0, 0, 0.5, 0), e8: F(0.55, 0.05, 0.55, 0.48, 0.85), links: F(0.06, 0.12, 0.65, 0.42, 0.55), }, }, }, { name: 'B2_triangular-rosette-cluster', label: 'B2 · three rosettes in a triangle · varied sizes', overrides: { paperOverride: paper([222, 210, 220]), vignOverride: [55, 40, 60], featureOverride: [65, 45, 75], stroke: 1.6, cubicScale: 1.4, schlegelScale: 0.65, e8Origins: [ { x: 0.00, y: -0.45, scale: 0.50 }, { x: -0.45, y: 0.30, scale: 0.32 }, { x: 0.45, y: 0.30, scale: 0.32 }, ], linkCount: 6, fields: { cubic: F(0.78, 0.92, 0.50, 0.38, 0.40), schlegel: F(0.82, 0.92, 0.40, 0.40, 0.50), e8: F(0.82, 0.95, 0.55, 0.46, 0.85), links: F(0.85, 0.96, 0.65, 0.42, 0.55), }, }, }, { name: 'B3_five-rosettes-quincunx', label: 'B3 · quincunx: 4 corners + centre · five rosettes total', overrides: { paperOverride: paper([232, 222, 205]), vignOverride: [60, 50, 35], featureOverride: [55, 45, 30], stroke: 1.6, cubicScale: 1.3, schlegelScale: 0, e8Origins: [ { x: -0.50, y: -0.50, scale: 0.26 }, { x: 0.50, y: -0.50, scale: 0.26 }, { x: 0.00, y: 0.00, scale: 0.42 }, { x: -0.50, y: 0.50, scale: 0.26 }, { x: 0.50, y: 0.50, scale: 0.26 }, ], linkCount: 8, fields: { cubic: F(0.10, 0.16, 0.40, 0.40, 0.35), schlegel: F(0, 0, 0, 0.5, 0), e8: F(0.10, 0.16, 0.60, 0.45, 0.85), links: F(0.06, 0.12, 0.65, 0.42, 0.55), }, }, }, { name: 'B4_central-large-plus-orbit', label: 'B4 · one large central rosette + smaller orbiting satellites', overrides: { paperOverride: paper([225, 215, 198]), vignOverride: [60, 50, 35], featureOverride: [55, 45, 30], stroke: 1.6, gradientMode: 'radial', cubicScale: 1.4, schlegelScale: 0, e8Origins: [ { x: 0.00, y: 0.00, scale: 0.70 }, { x: 0.55, y: -0.55, scale: 0.18 }, { x: -0.55, y: -0.55, scale: 0.18 }, { x: 0.00, y: 0.62, scale: 0.18 }, ], linkCount: 6, fields: { cubic: F(0.55, 0.05, 0.55, 0.38, 0.35), schlegel: F(0, 0, 0, 0.5, 0), e8: F(0.55, 0.05, 0.55, 0.46, 0.85), links: F(0.06, 0.12, 0.70, 0.42, 0.55), }, }, }, // ────────────────── BLOCK C · lighter-on-midtone ───────────────────────────── { name: 'C1_lighter-on-deep-cream', label: 'C1 · lighter rosette on DEEP-cream midtone', overrides: { paperOverride: paper([180, 165, 140]), // mid-tone deep cream vignOverride: [90, 70, 45], featureOverride: [240, 230, 215], stroke: 2.4, cubicScale: 1.45, schlegelScale: 0.75, e8Origins: [{ x: 0, y: 0, scale: 0.78 }], fields: { cubic: F(0.06, 0.13, 0.30, 0.38, 0.55), schlegel: F(0.08, 0.10, 0.30, 0.38, 0.50), e8: F(0.08, 0.13, 0.20, 0.88, 0.80), // LIGHTER than the midtone paper links: F(0.06, 0.12, 0.40, 0.92, 0.85), }, }, }, { name: 'C2_lighter-on-warm-grey', label: 'C2 · lighter rosette on warm-grey midtone', overrides: { paperOverride: paper([155, 150, 140]), // warm grey midtone vignOverride: [80, 70, 55], featureOverride: [235, 230, 220], stroke: 2.6, cubicScale: 1.55, schlegelScale: 0, e8Origins: [{ x: -0.20, y: 0, scale: 0.55 }, { x: 0.20, y: 0, scale: 0.55 }], fields: { cubic: F(0.06, 0.12, 0.20, 0.40, 0.55), schlegel: F(0, 0, 0, 0.5, 0), e8: F(0.06, 0.12, 0.18, 0.90, 0.80), // pale ivory on grey links: F(0.06, 0.12, 0.30, 0.94, 0.85), }, }, }, { name: 'C3_lighter-on-sage', label: 'C3 · lighter rosette on sage-grey midtone', overrides: { paperOverride: paper([155, 165, 145]), // sage midtone vignOverride: [55, 70, 50], featureOverride: [225, 235, 215], stroke: 2.4, cubicScale: 1.4, schlegelScale: 0.75, e8Origins: [{ x: 0, y: 0, scale: 0.82 }], fields: { cubic: F(0.40, 0.48, 0.30, 0.42, 0.55), schlegel: F(0.42, 0.48, 0.25, 0.40, 0.50), e8: F(0.40, 0.48, 0.22, 0.90, 0.80), links: F(0.40, 0.50, 0.35, 0.94, 0.85), }, }, }, // ────────────────── BLOCK D · plain white on light ─────────────────────────── { name: 'D1_thick-white-on-cream', label: 'D1 · bold WHITE strokes on warm cream — embossed', overrides: { paperOverride: paper([222, 210, 188]), vignOverride: [70, 55, 35], featureOverride: [60, 50, 35], stroke: 3.4, // thick cubicScale: 1.45, schlegelScale: 1.20, e8Origins: [{ x: 0, y: 0, scale: 0.72 }], fields: { cubic: WHITE(0.85), schlegel: WHITE(0.65), e8: WHITE(0.92), links: WHITE(0.78), }, }, }, { name: 'D2_white-on-pale-rose', label: 'D2 · plain white rosettes on pale rose', overrides: { paperOverride: paper([232, 210, 210]), vignOverride: [80, 55, 55], featureOverride: [90, 55, 55], stroke: 3.2, cubicScale: 1.35, schlegelScale: 0, e8Origins: [{ x: 0, y: 0, scale: 0.95 }, { x: 0, y: 0, scale: 0.60 }, { x: 0, y: 0, scale: 0.30 }], linkCount: 0, fields: { cubic: WHITE(0.40), // very subtle cubic atmosphere schlegel: F(0, 0, 0, 0.5, 0), e8: WHITE(0.95), // strong solid white rosettes links: WHITE(0.5), }, }, }, { name: 'D3_white-on-pale-blue', label: 'D3 · white schlegel + white rosette on pale blue', overrides: { paperOverride: paper([190, 215, 230]), vignOverride: [40, 55, 70], featureOverride: [30, 50, 75], stroke: 3.0, cubicScale: 1.0, schlegelScale: 1.50, // schlegel prominent e8Origins: [{ x: 0, y: 0, scale: 0.65 }], fields: { cubic: WHITE(0.45), schlegel: WHITE(0.85), // bold white tesseract e8: WHITE(0.92), links: WHITE(0.55), }, }, }, // ────────────────── BLOCK E · schlegel plays ───────────────────────────────── { name: 'E1_schlegel-thin-nested', label: 'E1 · Schlegel: deep nesting (tiny inner cube) · thin lines', overrides: { paperOverride: paper([232, 222, 200]), vignOverride: [60, 50, 40], featureOverride: [60, 50, 35], stroke: 1.4, cubicScale: 0.55, schlegelScale: 1.40, schlegelInnerR: 0.08, // very small inner cube → strong perspective schlegelRot3D: 0.20, e8Origins: [{ x: 0, y: 0, scale: 0.42 }], fields: { cubic: F(0.58, 0.62, 0.30, 0.45, 0.40), schlegel: F(0.60, 0.68, 0.50, 0.32, 0.78), // schlegel is the subject e8: F(0.55, 0.65, 0.50, 0.45, 0.78), links: F(0.58, 0.66, 0.65, 0.42, 0.55), }, }, }, { name: 'E2_schlegel-shifted-axis-thick', label: 'E2 · Schlegel with strongly rotated 4D axis · thick lines', overrides: { paperOverride: paper([222, 212, 195]), vignOverride: [70, 55, 40], featureOverride: [60, 45, 30], stroke: 3.0, // thick cubicScale: 0.65, schlegelScale: 1.55, schlegelInnerR: 0.42, // larger inner cube — less perspective, more parallel schlegelRot3D: 1.10, // strong rotation — looking at the tesseract from different angle schlegelOriginX: 0.15, e8Origins: [{ x: -0.45, y: -0.45, scale: 0.32 }], fields: { cubic: F(0.06, 0.12, 0.35, 0.40, 0.40), schlegel: F(0.06, 0.13, 0.55, 0.32, 0.85), e8: F(0.08, 0.16, 0.65, 0.45, 0.85), links: F(0.06, 0.12, 0.65, 0.40, 0.55), }, }, }, { name: 'E3_dual-schlegels-rotated', label: 'E3 · BIG schlegel offset · faint thin counter-rotated cubic', overrides: { paperOverride: paper([200, 200, 215]), // pale lavender-grey midtone vignOverride: [60, 55, 75], featureOverride: [40, 40, 60], stroke: 2.6, cubicScale: 1.6, cubicRot: 0.80, // cubic strongly rotated schlegelScale: 1.85, // schlegel very large bleeding schlegelInnerR: 0.55, // inner cube larger schlegelRot3D: -0.30, // negative rotation — opposite angle schlegelOriginX: -0.10, schlegelOriginY: 0.10, e8Origins: [{ x: 0.40, y: -0.40, scale: 0.32 }, { x: -0.40, y: 0.40, scale: 0.32 }], fields: { cubic: F(0.62, 0.70, 0.30, 0.55, 0.35), schlegel: F(0.62, 0.72, 0.55, 0.32, 0.78), e8: F(0.65, 0.75, 0.60, 0.45, 0.85), links: F(0.62, 0.78, 0.70, 0.42, 0.55), }, }, }, ]; const base = paramsFromSeed(BASE_SEED); for (const v of variations) { const params = { ...base, ...v.overrides }; if (v.overrides.fields) params.fields = v.overrides.fields; const svg = renderQFTSVG(generateQFTScene(params), params, SIZE); const path = `${OUT_DIR}/${v.name}.svg`; writeFileSync(path, svg); console.log(`ok ${v.name}`); } console.log(`\nrendered ${variations.length} variations → ${OUT_DIR}/`);