Added QFT
This commit is contained in:
190
src/qft/scene.js
Normal file
190
src/qft/scene.js
Normal file
@@ -0,0 +1,190 @@
|
||||
/* ============================================================
|
||||
qft/scene.js — assemble a QFT plate from params.
|
||||
Per-field origins (each lattice movable), multi-E8 clusters
|
||||
scattered around the canvas, and explicit cross-field LINKS
|
||||
(Feynman propagator symbols connecting a vertex in one field
|
||||
to a vertex in another — the "interactions" between fields).
|
||||
|
||||
Returns:
|
||||
{ grids: [...], links: [...], hash, plate, exposure, lab }
|
||||
Each grid: { id, propagator, vertices:[{x,y}], edges:[{a,b}] }
|
||||
Each link: { a:{x,y}, b:{x,y}, propagator: 'photon'|'scalar'|'gluon' }
|
||||
============================================================ */
|
||||
import { makeRng, cyrb53, pick } from '../rng.js';
|
||||
import { buildCubic, buildSchlegel, buildE8, buildNautilus, buildRipples } from './topology.js';
|
||||
import { distortVertices, applyWavepackets, applyVortices, applyStandingWaves } from './distortion.js';
|
||||
|
||||
const LABS = [
|
||||
'LATTICE QFT · LANL',
|
||||
'BNL · ALGEBRAIC GEOMETRY GROUP',
|
||||
'IHES · BURES-SUR-YVETTE',
|
||||
'PERIMETER INSTITUTE · WATERLOO',
|
||||
'INST. THEORETICAL PHYSICS · COPENHAGEN',
|
||||
];
|
||||
|
||||
function rotateScale(verts, angle, scale) {
|
||||
const c = Math.cos(angle), s = Math.sin(angle);
|
||||
for (const v of verts) {
|
||||
const x = v.x * c - v.y * s;
|
||||
const y = v.x * s + v.y * c;
|
||||
v.x = x * scale; v.y = y * scale;
|
||||
}
|
||||
}
|
||||
function translateVerts(verts, dx, dy) {
|
||||
for (const v of verts) { v.x += dx; v.y += dy; }
|
||||
}
|
||||
|
||||
export function generateQFTScene(params) {
|
||||
// ---- cubic ---- (cubicN controls density: 1=27v, 2=125v, 3=343v)
|
||||
const cubic = buildCubic(Math.max(1, params.cubicN | 0));
|
||||
rotateScale(cubic.vertices, params.cubicRot, params.cubicScale);
|
||||
translateVerts(cubic.vertices, params.cubicOriginX, params.cubicOriginY);
|
||||
cubic.id = 'cubic'; cubic.propagator = 'photon';
|
||||
|
||||
// ---- schlegel ---- (outer/inner radius and 3D rotation now parameterised)
|
||||
const schl = buildSchlegel(
|
||||
params.schlegelOuterR ?? 0.78,
|
||||
params.schlegelInnerR ?? 0.32,
|
||||
params.schlegelRot3D ?? 0.42,
|
||||
);
|
||||
rotateScale(schl.vertices, params.schlegelRot, params.schlegelScale);
|
||||
translateVerts(schl.vertices, params.schlegelOriginX, params.schlegelOriginY);
|
||||
schl.id = 'schlegel'; schl.propagator = 'scalar';
|
||||
|
||||
// ---- E8 clusters ----
|
||||
// If params.e8Origins (array of {x,y}) is provided, place clusters exactly
|
||||
// at those positions (count comes from the array length). Otherwise auto-
|
||||
// distribute on a ring of radius e8OriginRadius.
|
||||
const e8Rng = makeRng(params.seed, 'qft-e8');
|
||||
const e8Instances = [];
|
||||
let e8Origins;
|
||||
if (Array.isArray(params.e8Origins) && params.e8Origins.length) {
|
||||
e8Origins = params.e8Origins;
|
||||
} else {
|
||||
const N = Math.max(1, params.e8Count | 0);
|
||||
e8Origins = [];
|
||||
const angleJitter = 0.35;
|
||||
for (let i = 0; i < N; i++) {
|
||||
const baseAng = (i / N) * Math.PI * 2;
|
||||
const ang = baseAng + (e8Rng() - 0.5) * angleJitter;
|
||||
const radius = N === 1 ? 0 : params.e8OriginRadius;
|
||||
e8Origins.push({ x: Math.cos(ang) * radius, y: Math.sin(ang) * radius });
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < e8Origins.length; i++) {
|
||||
const o = e8Origins[i];
|
||||
// per-cluster scale override: e8Origins[i] may include {x, y, scale}
|
||||
const clusterScale = (typeof o.scale === 'number') ? o.scale : params.e8Scale;
|
||||
// pick rosette geometry: open NAUTILUS spiral (default for chaotic-growth feel)
|
||||
// or closed concentric RINGS (the older E8-Coxeter-style mandala).
|
||||
const style = params.e8Style ?? 'nautilus';
|
||||
const e = style === 'rings'
|
||||
? buildE8(4, 18)
|
||||
: buildNautilus(params.nautilusTurns ?? 2.6,
|
||||
params.nautilusPerTurn ?? 14,
|
||||
params.nautilusGrowth ?? 0.21);
|
||||
rotateScale(e.vertices, params.e8Rot + (e8Rng() - 0.5) * 0.6, clusterScale);
|
||||
translateVerts(e.vertices, o.x, o.y);
|
||||
e.id = 'e8';
|
||||
e.instance = i;
|
||||
e.propagator = 'gluon';
|
||||
e8Instances.push(e);
|
||||
}
|
||||
|
||||
// ---- RIPPLES — concentric wavefronts from source points ----
|
||||
// params.ripples: [{ x, y, count?, r0?, dR?, propagator? }]
|
||||
const rippleInstances = [];
|
||||
if (Array.isArray(params.ripples)) {
|
||||
for (let i = 0; i < params.ripples.length; i++) {
|
||||
const rs = params.ripples[i];
|
||||
const r = buildRipples(
|
||||
{ x: rs.x, y: rs.y },
|
||||
rs.count ?? 6,
|
||||
rs.r0 ?? 0.06,
|
||||
rs.dR ?? 0.09,
|
||||
rs.segments ?? 40,
|
||||
);
|
||||
r.id = 'ripple';
|
||||
r.instance = i;
|
||||
r.propagator = rs.propagator ?? 'photon';
|
||||
rippleInstances.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
const allFields = [cubic, schl, ...e8Instances, ...rippleInstances];
|
||||
|
||||
// ---- FIELD PERTURBATIONS — applied to cubic + schlegel after scale/rotate ----
|
||||
// wavepackets: Gaussian bulge/dimple (Schrödinger particle as field feature)
|
||||
if (Array.isArray(params.wavepackets) && params.wavepackets.length) {
|
||||
applyWavepackets(cubic.vertices, params.wavepackets);
|
||||
applyWavepackets(schl.vertices, params.wavepackets);
|
||||
}
|
||||
// vortices: swirling Gaussian rotation (angular momentum / spin)
|
||||
if (Array.isArray(params.vortices) && params.vortices.length) {
|
||||
applyVortices(cubic.vertices, params.vortices);
|
||||
applyVortices(schl.vertices, params.vortices);
|
||||
}
|
||||
// standing waves: global plane-wave sinusoidal modulation (Chladni / nodal lines)
|
||||
if (Array.isArray(params.standingWaves) && params.standingWaves.length) {
|
||||
applyStandingWaves(cubic.vertices, params.standingWaves);
|
||||
applyStandingWaves(schl.vertices, params.standingWaves);
|
||||
}
|
||||
|
||||
// distortion (per-field salt so neighbouring lattices warp differently)
|
||||
for (const g of allFields) {
|
||||
const salt = 'dist:' + g.id + (g.instance != null ? ':' + g.instance : '');
|
||||
distortVertices(g.vertices, params.distRCalm, params.distRMax, params.distStrength,
|
||||
params.seed, salt);
|
||||
}
|
||||
|
||||
// composition offset — translate the whole scene on the paper
|
||||
if (params.compositionOffsetX || params.compositionOffsetY) {
|
||||
for (const g of allFields) translateVerts(g.vertices, params.compositionOffsetX, params.compositionOffsetY);
|
||||
}
|
||||
|
||||
// ---- cross-field links ----
|
||||
const linkRng = makeRng(params.seed, 'qft-links');
|
||||
const propChoices = ['photon', 'scalar', 'gluon'];
|
||||
const linkCount = Math.max(0, params.linkCount | 0);
|
||||
const links = [];
|
||||
// pair fields by their INDEX in allFields so multi-E8 clusters can interact
|
||||
// with cubic/schlegel and with each other
|
||||
for (let i = 0; i < linkCount && allFields.length >= 2; i++) {
|
||||
let f1Idx, f2Idx, attempts = 0;
|
||||
do {
|
||||
f1Idx = Math.floor(linkRng() * allFields.length);
|
||||
f2Idx = Math.floor(linkRng() * allFields.length);
|
||||
attempts++;
|
||||
} while ((f1Idx === f2Idx || allFields[f1Idx].id === allFields[f2Idx].id) && attempts < 20);
|
||||
const f1 = allFields[f1Idx], f2 = allFields[f2Idx];
|
||||
const v1 = f1.vertices[Math.floor(linkRng() * f1.vertices.length)];
|
||||
const v2 = f2.vertices[Math.floor(linkRng() * f2.vertices.length)];
|
||||
const prop = params.linkPropagator === 'mixed'
|
||||
? propChoices[Math.floor(linkRng() * propChoices.length)]
|
||||
: (params.linkPropagator || 'photon');
|
||||
// per-link curvature: random sign × random magnitude up to params.linkCurvature.
|
||||
// a fraction of links stay straight (curvature=0) so the result is mixed-tangle.
|
||||
let curvature = 0;
|
||||
const lc = params.linkCurvature ?? 0;
|
||||
if (lc > 1e-3) {
|
||||
// 30% straight, 70% curved; signed
|
||||
if (linkRng() > 0.30) {
|
||||
const sign = linkRng() < 0.5 ? -1 : 1;
|
||||
curvature = sign * lc * (0.45 + linkRng() * 0.55);
|
||||
}
|
||||
}
|
||||
links.push({ a: { x: v1.x, y: v1.y }, b: { x: v2.x, y: v2.y }, propagator: prop, curvature });
|
||||
}
|
||||
|
||||
// archival metadata
|
||||
const hash = cyrb53(params.seed);
|
||||
const ds = parseInt(hash.slice(0, 8), 16);
|
||||
const plate = (parseInt(hash.slice(-3), 16) % 999).toString().padStart(3, '0');
|
||||
const year = 1985 + (ds % 30);
|
||||
const month = 1 + ((ds >> 4) % 12);
|
||||
const day = 1 + ((ds >> 8) % 28);
|
||||
const exposure = `${year}.${String(month).padStart(2, '0')}.${String(day).padStart(2, '0')}`;
|
||||
const lab = pick(makeRng(params.seed, 'qft-lab'), LABS);
|
||||
|
||||
return { grids: allFields, links, hash, plate, exposure, lab };
|
||||
}
|
||||
Reference in New Issue
Block a user