Added QFT

This commit is contained in:
2026-05-29 15:40:42 -04:00
parent e59b024b62
commit 56c59a1f9c
148 changed files with 428741 additions and 7 deletions

190
src/qft/scene.js Normal file
View 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 };
}