PageSourceSearch

https://biodynai.com/about-figures.js

js biodynai.com collected 2026-10-08 10:07:39 UTC 26,930 bytes, 843 lines download raw bytes

1/* ========================================
2   BIODYN, About, symbolic figures
3   Each figure has to read as the thing it argues,
4   not as an abstract cloud:
5
6     I    a small organism, traced end to end
7     II   an opaque output surface, cut open to
8          show the interior it hides
9     III  disordered internals on one side, a
10          double helix drawn out of them
11
12   and one figure per application area, so the
13   detail view can morph a level deeper.
14   ======================================== */
15
16(() => {
17  'use strict';
18
19  const A = window.BiodynAbout;
20  if (!A) return;
21
22  const TAU = Math.PI * 2;
23
24  /* Places graph nodes first, then scatters the remaining budget along the
25     edges so the wiring reads as drawn rather than implied. */
26  const fromGraph = (nodes, edges, n, options) => {
27    const opts = options || {};
28    const rand = opts.rand || A.makeRandom(1);
29    const jitter = opts.jitter === undefined ? 0.006 : opts.jitter;
30    const fill = opts.fill === undefined ? 0.3 : opts.fill;
31    const pos = new Float32Array(n * 3);
32    const intensity = new Float32Array(n);
33    const nodeCount = Math.min(nodes.length, n);
34
35    for (let i = 0; i < nodeCount; i += 1) {
36      pos[i * 3] = nodes[i][0];
37      pos[i * 3 + 1] = nodes[i][1];
38      pos[i * 3 + 2] = nodes[i][2];
39      intensity[i] = opts.nodeIntensity ? opts.nodeIntensity(i) : 0.95;
40    }
41
42    const edgeCount = edges.length / 2;
43    for (let i = nodeCount; i < n; i += 1) {
44      const e = edgeCount ? (i - nodeCount) % edgeCount : 0;
45      const a = nodes[edges[e * 2]] || [0, 0, 0];
46      const b = nodes[edges[e * 2 + 1]] || [0, 0, 0];
47      const t = rand();
48      pos[i * 3] = a[0] + (b[0] - a[0]) * t + A.gaussian(rand) * jitter;
49      pos[i * 3 + 1] = a[1] + (b[1] - a[1]) * t + A.gaussian(rand) * jitter;
50      pos[i * 3 + 2] = a[2] + (b[2] - a[2]) * t + A.gaussian(rand) * jitter;
51      intensity[i] = typeof fill === 'function' ? fill(i) : fill;
52    }
53
54    return { pos, intensity, edges: nodeCount >= nodes.length ? edges : null };
55  };
56
57  /* =======================================
58     I, A small organism, traced end to end
59     ======================================= */
60
61  const modelOrganism = (n) => {
62    const rand = A.makeRandom(31337);
63    const CORD = 34;
64    const GANGLIA = 22;
65    const nodes = [];
66    const edges = [];
67
68    // Tapered body profile, fatter at the head.
69    const radiusAt = (t) => 0.42 * Math.pow(Math.sin(Math.PI * Math.min(1, t * 1.06)), 0.62)
70      * (1 + 0.42 * Math.exp(-Math.pow((t - 0.16) / 0.16, 2)));
71
72    // Dorsal nerve cord down the length of the animal.
73    for (let i = 0; i < CORD; i += 1) {
74      const t = i / (CORD - 1);
75      nodes.push([(t - 0.5) * 1.68, radiusAt(t) * 0.42 + 0.02, 0]);
76      if (i > 0) edges.push(i - 1, i);
77    }
78
79    // Ganglia hanging off the cord, alternating left and right.
80    for (let g = 0; g < GANGLIA; g += 1) {
81      const anchor = 2 + Math.floor((g / GANGLIA) * (CORD - 4));
82      const side = g % 2 === 0 ? 1 : -1;
83      const t = anchor / (CORD - 1);
84      const r = radiusAt(t);
85      const index = nodes.length;
86      nodes.push([
87        (t - 0.5) * 1.68 + A.gaussian(rand) * 0.02,
88        r * -0.24,
89        side * r * 0.82
90      ]);
91      edges.push(anchor, index);
92      if (g >= 2) edges.push(index - 2, index);
93    }
94
95    const built = fromGraph(nodes, edges, n, {
96      rand,
97      jitter: 0.008,
98      nodeIntensity: (i) => (i < CORD ? 1 : 0.82),
99      fill: 0.4
100    });
101
102    // Body wall, dim, so the silhouette of the animal is legible.
103    const surfaceStart = Math.min(nodes.length, n) + Math.floor((n - nodes.length) * 0.34);
104    for (let i = surfaceStart; i < n; i += 1) {
105      const t = rand();
106      const a = rand() * TAU;
107      const r = radiusAt(t);
108      built.pos[i * 3] = (t - 0.5) * 1.68;
109      built.pos[i * 3 + 1] = Math.sin(a) * r;
110      built.pos[i * 3 + 2] = Math.cos(a) * r;
111      built.intensity[i] = 0.24;
112    }
113
114    A.fit(built.pos, n, 1);
115    return built;
116  };
117
118  /* =======================================
119     II, Output surface, cut open
120     ======================================= */
121
122  const cutawayAudit = (n) => {
123    const rand = A.makeRandom(556677);
124    const CORE_NODES = 46;
125    const nodes = [];
126    const edges = [];
127
128    // A compact, wired interior: organised knowledge, not noise.
129    for (let i = 0; i < CORE_NODES; i += 1) {
130      const phi = Math.acos(1 - (2 * (i + 0.5)) / CORE_NODES);
131      const theta = Math.PI * (1 + Math.sqrt(5)) * (i + 0.5);
132      const r = 0.28 + rand() * 0.2;
133      nodes.push([
134        r * Math.sin(phi) * Math.cos(theta),
135        r * Math.cos(phi),
136        r * Math.sin(phi) * Math.sin(theta)
137      ]);
138    }
139    for (let i = 0; i < CORE_NODES; i += 1) {
140      for (let j = i + 1; j < CORE_NODES; j += 1) {
141        const d = Math.hypot(
142          nodes[i][0] - nodes[j][0],
143          nodes[i][1] - nodes[j][1],
144          nodes[i][2] - nodes[j][2]
145        );
146        if (d < 0.31) edges.push(i, j);
147      }
148    }
149
150    const built = fromGraph(nodes, edges, n, {
151      rand,
152      jitter: 0.005,
153      nodeIntensity: () => 1,
154      fill: 0.55
155    });
156
157    // The shell is the output surface. A wedge is missing, so you can see that
158    // there is far more inside than the surface ever shows.
159    const shellStart = Math.floor(n * 0.46);
160    const GAP_FROM = -0.62;
161    const GAP_TO = 0.72;
162    for (let i = shellStart; i < n; i += 1) {
163      let theta = 0;
164      let phi = 0;
165      // Rejection-sample the sphere minus the wedge.
166      for (let tries = 0; tries < 24; tries += 1) {
167        const k = rand();
168        phi = Math.acos(1 - 2 * k);
169        theta = rand() * TAU - Math.PI;
170        if (theta < GAP_FROM || theta > GAP_TO) break;
171      }
172      const r = 1 + A.gaussian(rand) * 0.01;
173      built.pos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
174      built.pos[i * 3 + 1] = r * Math.cos(phi);
175      built.pos[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
176      built.intensity[i] = 0.26;
177    }
178
179    A.fit(built.pos, n, 1);
180    return built;
181  };
182
183  /* =======================================
184     III, A helix drawn out of the internals
185     ======================================= */
186
187  const extraction = (n) => {
188    const rand = A.makeRandom(8080808);
189    const TURNS = 2.6;
190    const RUNGS = 26;
191    const nodes = [];
192    const edges = [];
193    const strandA = [];
194    const strandB = [];
195
196    // Ordered structure, extracted: a double helix standing on the right.
197    for (let i = 0; i < RUNGS; i += 1) {
198      const t = i / (RUNGS - 1);
199      const angle = t * TAU * TURNS;
200      const y = -0.86 + t * 1.72;
201      const x = 0.52;
202      strandA.push(nodes.length);
203      nodes.push([x + Math.cos(angle) * 0.26, y, Math.sin(angle) * 0.26]);
204      strandB.push(nodes.length);
205      nodes.push([x - Math.cos(angle) * 0.26, y, -Math.sin(angle) * 0.26]);
206      edges.push(strandA[i], strandB[i]);
207      if (i > 0) {
208        edges.push(strandA[i - 1], strandA[i]);
209        edges.push(strandB[i - 1], strandB[i]);
210      }
211    }
212
213    const built = fromGraph(nodes, edges, n, {
214      rand,
215      jitter: 0.005,
216      nodeIntensity: () => 0.95,
217      fill: 0.5
218    });
219
220    const helixBudget = Math.floor(n * 0.52);
221
222    // The internals it was read out of: a diffuse, unordered mass on the left.
223    const cloudEnd = Math.floor(n * 0.84);
224    for (let i = helixBudget; i < cloudEnd; i += 1) {
225      built.pos[i * 3] = -0.78 + A.gaussian(rand) * 0.29;
226      built.pos[i * 3 + 1] = -0.04 + A.gaussian(rand) * 0.34;
227      built.pos[i * 3 + 2] = A.gaussian(rand) * 0.29;
228      built.intensity[i] = 0.24;
229    }
230
231    // The stream between them: the extraction itself.
232    for (let i = cloudEnd; i < n; i += 1) {
233      const t = rand();
234      const x = -0.5 + t * 0.98;
235      const spread = 0.2 * (1 - t) + 0.03;
236      built.pos[i * 3] = x;
237      built.pos[i * 3 + 1] = -0.05 + A.gaussian(rand) * spread + Math.sin(t * 2.6) * 0.06;
238      built.pos[i * 3 + 2] = A.gaussian(rand) * spread;
239      built.intensity[i] = 0.34 + t * 0.5;
240    }
241
242    A.fit(built.pos, n, 1);
243    return built;
244  };
245
246  /* =======================================
247     Application figures
248     ======================================= */
249
250  /* Layered, directed: regulators above, targets below. */
251  const grnFigure = (n) => {
252    const rand = A.makeRandom(4242);
253    const LAYERS = [9, 14, 18];
254    const nodes = [];
255    const edges = [];
256    const layerIndex = [];
257
258    LAYERS.forEach((size, l) => {
259      const y = 0.72 - l * 0.72;
260      const ring = [];
261      for (let i = 0; i < size; i += 1) {
262        const a = (i / size) * TAU + l * 0.4;
263        const r = 0.34 + l * 0.24;
264        ring.push(nodes.length);
265        nodes.push([Math.cos(a) * r, y + A.gaussian(rand) * 0.03, Math.sin(a) * r]);
266      }
267      layerIndex.push(ring);
268    });
269
270    for (let l = 0; l < layerIndex.length - 1; l += 1) {
271      layerIndex[l].forEach((from) => {
272        const below = layerIndex[l + 1];
273        const fanout = 2 + Math.floor(rand() * 2);
274        for (let k = 0; k < fanout; k += 1) {
275          edges.push(from, below[Math.floor(rand() * below.length)]);
276        }
277      });
278    }
279    // A few skip edges, because real networks are not tidy.
280    for (let k = 0; k < 6; k += 1) {
281      edges.push(
282        layerIndex[0][Math.floor(rand() * layerIndex[0].length)],
283        layerIndex[2][Math.floor(rand() * layerIndex[2].length)]
284      );
285    }
286
287    const built = fromGraph(nodes, edges, n, {
288      rand,
289      nodeIntensity: (i) => (i < LAYERS[0] ? 1 : 0.7),
290      fill: 0.3
291    });
292    A.fit(built.pos, n, 1);
293    return built;
294  };
295
296  /* A long trajectory that thins with time, and the points that hold. */
297  const longevityFigure = (n) => {
298    const rand = A.makeRandom(19191);
299    const STEPS = 78;
300    const nodes = [];
301    const edges = [];
302
303    for (let i = 0; i < STEPS; i += 1) {
304      const t = i / (STEPS - 1);
305      const angle = t * TAU * 3.1;
306      const r = 0.52 - t * 0.33;
307      nodes.push([(t - 0.5) * 1.9, Math.sin(angle) * r, Math.cos(angle) * r]);
308      if (i > 0) edges.push(i - 1, i);
309    }
310
311    const built = fromGraph(nodes, edges, n, {
312      rand,
313      jitter: 0.007,
314      // Signal decays along the trajectory; a periodic few stay bright.
315      nodeIntensity: (i) => (i % 9 === 0 ? 1 : 0.9 - (i / STEPS) * 0.62),
316      fill: (i) => 0.34 - (i / n) * 0.12
317    });
318    A.fit(built.pos, n, 1);
319    return built;
320  };
321
322  /* One neuron: soma, dendritic arbor above, axon and terminals below. */
323  const neuroFigure = (n) => {
324    const rand = A.makeRandom(6501);
325    const nodes = [[0, -0.05, 0]];
326    const edges = [];
327
328    const deflect = (dir, spread) => {
329      const v = [
330        dir[0] + A.gaussian(rand) * spread,
331        dir[1] + A.gaussian(rand) * spread * 0.5,
332        dir[2] + A.gaussian(rand) * spread
333      ];
334      const len = Math.hypot(v[0], v[1], v[2]) || 1;
335      return [v[0] / len, v[1] / len, v[2] / len];
336    };
337
338    const grow = (from, dir, length, gen, max) => {
339      const p = nodes[from];
340      const to = [p[0] + dir[0] * length, p[1] + dir[1] * length, p[2] + dir[2] * length];
341      const index = nodes.length;
342      nodes.push(to);
343      edges.push(from, index);
344      if (gen >= max) return;
345      const kids = gen < 1 ? 2 : (rand() < 0.55 ? 2 : 3);
346      for (let k = 0; k < kids; k += 1) grow(index, deflect(dir, 0.62), length * 0.74, gen + 1, max);
347    };
348
349    // Dendrites fan upward.
350    for (let d = 0; d < 4; d += 1) {
351      const a = (d / 4) * TAU;
352      grow(0, deflect([Math.cos(a) * 0.5, 1, Math.sin(a) * 0.5], 0.2), 0.34, 0, 3);
353    }
354
355    // A single axon runs down, then breaks into terminals.
356    let axon = 0;
357    for (let s = 0; s < 7; s += 1) {
358      const p = nodes[axon];
359      const index = nodes.length;
360      nodes.push([p[0] + A.gaussian(rand) * 0.02, p[1] - 0.15, p[2] + A.gaussian(rand) * 0.02]);
361      edges.push(axon, index);
362      axon = index;
363    }
364    for (let d = 0; d < 5; d += 1) {
365      const a = (d / 5) * TAU;
366      grow(axon, deflect([Math.cos(a) * 0.8, -0.5, Math.sin(a) * 0.8], 0.2), 0.2, 1, 2);
367    }
368
369    const built = fromGraph(nodes, edges, n, {
370      rand,
371      jitter: 0.006,
372      nodeIntensity: (i) => (i === 0 ? 1 : 0.62),
373      fill: 0.3
374    });
375
376    // Soma.
377    const somaStart = Math.floor(n * 0.9);
378    for (let i = somaStart; i < n; i += 1) {
379      built.pos[i * 3] = A.gaussian(rand) * 0.07;
380      built.pos[i * 3 + 1] = -0.05 + A.gaussian(rand) * 0.07;
381      built.pos[i * 3 + 2] = A.gaussian(rand) * 0.07;
382      built.intensity[i] = 1;
383    }
384
385    A.fit(built.pos, n, 1);
386    return built;
387  };
388
389  /* A cell: membrane, nucleus, organelles, cytoskeleton. */
390  const cellFigure = (n) => {
391    const rand = A.makeRandom(770077);
392    // Few, faint spokes: the cytoskeleton should read behind the membrane and
393    // nucleus, not turn the cell into a starburst.
394    const SPOKES = 16;
395    const nodes = [];
396    const edges = [];
397
398    for (let i = 0; i < SPOKES; i += 1) {
399      const phi = Math.acos(1 - (2 * (i + 0.5)) / SPOKES);
400      const theta = Math.PI * (1 + Math.sqrt(5)) * (i + 0.5);
401      const dir = [
402        Math.sin(phi) * Math.cos(theta),
403        Math.cos(phi),
404        Math.sin(phi) * Math.sin(theta)
405      ];
406      const inner = nodes.length;
407      nodes.push([dir[0] * 0.3, dir[1] * 0.3, dir[2] * 0.3]);
408      const outer = nodes.length;
409      nodes.push([dir[0] * 0.93, dir[1] * 0.93, dir[2] * 0.93]);
410      edges.push(inner, outer);
411    }
412
413    const built = fromGraph(nodes, edges, n, {
414      rand,
415      jitter: 0.006,
416      nodeIntensity: (i) => (i % 2 === 0 ? 0.5 : 0.3),
417      fill: 0.13
418    });
419
420    const nucleusStart = Math.floor(n * 0.24);
421    const organelleStart = Math.floor(n * 0.5);
422    const membraneStart = Math.floor(n * 0.64);
423
424    for (let i = nucleusStart; i < organelleStart; i += 1) {
425      built.pos[i * 3] = A.gaussian(rand) * 0.16;
426      built.pos[i * 3 + 1] = A.gaussian(rand) * 0.16;
427      built.pos[i * 3 + 2] = A.gaussian(rand) * 0.16;
428      built.intensity[i] = 1;
429    }
430
431    const centres = [];
432    for (let k = 0; k < 6; k += 1) {
433      const phi = Math.acos(1 - (2 * (k + 0.5)) / 6);
434      const theta = Math.PI * (1 + Math.sqrt(5)) * (k + 0.5);
435      centres.push([
436        0.58 * Math.sin(phi) * Math.cos(theta),
437        0.58 * Math.cos(phi),
438        0.58 * Math.sin(phi) * Math.sin(theta)
439      ]);
440    }
441    for (let i = organelleStart; i < membraneStart; i += 1) {
442      const c = centres[i % centres.length];
443      built.pos[i * 3] = c[0] + A.gaussian(rand) * 0.09;
444      built.pos[i * 3 + 1] = c[1] + A.gaussian(rand) * 0.09;
445      built.pos[i * 3 + 2] = c[2] + A.gaussian(rand) * 0.09;
446      built.intensity[i] = 0.72;
447    }
448
449    for (let i = membraneStart; i < n; i += 1) {
450      const phi = Math.acos(1 - (2 * ((i - membraneStart) + 0.5)) / (n - membraneStart));
451      const theta = Math.PI * (1 + Math.sqrt(5)) * (i + 0.5);
452      const r = 1 + A.gaussian(rand) * 0.012;
453      built.pos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
454      built.pos[i * 3 + 1] = r * Math.cos(phi);
455      built.pos[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
456      built.intensity[i] = 0.38;
457    }
458
459    A.fit(built.pos, n, 1);
460    return built;
461  };
462
463  /* =======================================
464     Capability-audit figures
465     ======================================= */
466
467  /* Two readout surfaces at one budget: the sanctioned output above, the
468     internal layer below, and the measured distance between them. */
469  const gapFigure = (n) => {
470    const rand = A.makeRandom(2402);
471    const COLS = 11;
472    const nodes = [];
473    const edges = [];
474
475    for (let i = 0; i < COLS; i += 1) {
476      for (let j = 0; j < COLS; j += 1) {
477        const x = ((i / (COLS - 1)) - 0.5) * 1.7;
478        const z = ((j / (COLS - 1)) - 0.5) * 1.7;
479        if (Math.hypot(x, z) > 0.92) continue;
480        const upper = nodes.length;
481        nodes.push([x, 0.52, z]);
482        const lower = nodes.length;
483        nodes.push([x, -0.52, z]);
484        // Risers make the gap itself the thing you see.
485        if ((i + j) % 2 === 0) edges.push(upper, lower);
486      }
487    }
488
489    const built = fromGraph(nodes, edges, n, {
490      rand,
491      jitter: 0.008,
492      // Sanctioned surface sparse and dim; the internal layer dense and bright.
493      nodeIntensity: (i) => (i % 2 === 0 ? 0.3 : 1),
494      fill: 0.16
495    });
496
497    // Thicken the lower surface, so the two planes read as unequal.
498    const denseStart = Math.floor(n * 0.62);
499    for (let i = denseStart; i < n; i += 1) {
500      const a = rand() * TAU;
501      const r = Math.sqrt(rand()) * 0.9;
502      built.pos[i * 3] = Math.cos(a) * r;
503      built.pos[i * 3 + 1] = -0.52 + A.gaussian(rand) * 0.035;
504      built.pos[i * 3 + 2] = Math.sin(a) * r;
505      built.intensity[i] = 0.82;
506    }
507
508    A.fit(built.pos, n, 1);
509    return built;
510  };
511
512  /* Containment giving way: a bounded mass with part of it pulled out. */
513  const recoverabilityFigure = (n) => {
514    const rand = A.makeRandom(9931);
515    const RING = 30;
516    const nodes = [];
517    const edges = [];
518
519    // The boundary, broken over one arc.
520    for (let i = 0; i < RING; i += 1) {
521      const a = (i / RING) * TAU;
522      nodes.push([Math.cos(a) * 0.86, Math.sin(a) * 0.86, 0]);
523      const gap = a > 5.1 && a < 6.05;
524      if (i > 0 && !gap) edges.push(i - 1, i);
525    }
526
527    const built = fromGraph(nodes, edges, n, {
528      rand,
529      jitter: 0.006,
530      nodeIntensity: () => 0.34,
531      fill: 0.22
532    });
533
534    const held = Math.floor(n * 0.34);
535    const escaped = Math.floor(n * 0.78);
536
537    // Still contained.
538    for (let i = held; i < escaped; i += 1) {
539      const a = rand() * TAU;
540      const r = Math.sqrt(rand()) * 0.62;
541      built.pos[i * 3] = Math.cos(a) * r;
542      built.pos[i * 3 + 1] = Math.sin(a) * r;
543      built.pos[i * 3 + 2] = A.gaussian(rand) * 0.16;
544      built.intensity[i] = 0.5;
545    }
546
547    // Pulled out through the break, brightening as it goes.
548    for (let i = escaped; i < n; i += 1) {
549      const t = rand();
550      const a = 5.58 + A.gaussian(rand) * 0.13;
551      const r = 0.7 + t * 0.95;
552      built.pos[i * 3] = Math.cos(a) * r;
553      built.pos[i * 3 + 1] = Math.sin(a) * r;
554      built.pos[i * 3 + 2] = A.gaussian(rand) * 0.07;
555      built.intensity[i] = 0.55 + t * 0.45;
556    }
557
558    A.fit(built.pos, n, 1);
559    return built;
560  };
561
562  /* A sieve: six gates, and most of what enters does not come out. */
563  const standardFigure = (n) => {
564    const rand = A.makeRandom(60606);
565    const GATES = 6;
566    const PER = 22;
567    const nodes = [];
568    const edges = [];
569
570    for (let g = 0; g < GATES; g += 1) {
571      const y = 0.92 - (g / (GATES - 1)) * 1.84;
572      const radius = 0.78 - g * 0.085;
573      const first = nodes.length;
574      for (let i = 0; i < PER; i += 1) {
575        const a = (i / PER) * TAU;
576        nodes.push([Math.cos(a) * radius, y, Math.sin(a) * radius]);
577        if (i > 0) edges.push(first + i - 1, first + i);
578      }
579      edges.push(first + PER - 1, first);
580    }
581
582    const built = fromGraph(nodes, edges, n, {
583      rand,
584      jitter: 0.005,
585      nodeIntensity: (i) => 0.34 + 0.4 * (Math.floor(i / PER) / (GATES - 1)),
586      fill: 0.2
587    });
588
589    // The candidates. Each one stops at the gate that rejects it; a few reach
590    // the bottom, and those are the ones bright enough to see.
591    const streamStart = Math.floor(n * 0.56);
592    for (let i = streamStart; i < n; i += 1) {
593      const roll = rand();
594      const survived = roll > 0.78 ? GATES - 1 : Math.floor(Math.pow(roll, 0.7) * (GATES - 1));
595      const y = 0.92 - (survived / (GATES - 1)) * 1.84 + 0.06 + A.gaussian(rand) * 0.03;
596      const a = rand() * TAU;
597      const r = Math.sqrt(rand()) * (0.62 - survived * 0.07);
598      built.pos[i * 3] = Math.cos(a) * r;
599      built.pos[i * 3 + 1] = y;
600      built.pos[i * 3 + 2] = Math.sin(a) * r;
601      built.intensity[i] = survived === GATES - 1 ? 1 : 0.24;
602    }
603
604    A.fit(built.pos, n, 1);
605    return built;
606  };
607
608  /* The coverage grid: axes against modalities, mostly still empty. */
609  const coverageFigure = (n) => {
610    const rand = A.makeRandom(101010);
611    const AXES = 10;
612    const MODES = 5;
613    const DEPTH = 3;
614    const nodes = [];
615    const edges = [];
616
617    for (let a = 0; a < AXES; a += 1) {
618      for (let m = 0; m < MODES; m += 1) {
619        const x = ((a / (AXES - 1)) - 0.5) * 1.9;
620        const y = ((m / (MODES - 1)) - 0.5) * 1.0;
621        const index = nodes.length;
622        nodes.push([x, y, 0]);
623        if (a > 0) edges.push(index - MODES, index);
624        if (m > 0) edges.push(index - 1, index);
625      }
626    }
627
628    const built = fromGraph(nodes, edges, n, {
629      rand,
630      jitter: 0.004,
631      // Only the corner of the grid that has cards behind it is lit.
632      nodeIntensity: (i) => (Math.floor(i / MODES) < 2 && i % MODES < 1 ? 1 : 0.22),
633      fill: 0.12
634    });
635
636    // The filled cells get real depth; the rest stay a flat lattice.
637    const fillStart = Math.floor(n * 0.86);
638    for (let i = fillStart; i < n; i += 1) {
639      const a = Math.floor(rand() * 2);
640      const x = ((a / (AXES - 1)) - 0.5) * 1.9;
641      built.pos[i * 3] = x + A.gaussian(rand) * 0.05;
642      built.pos[i * 3 + 1] = -0.5 + A.gaussian(rand) * 0.05;
643      built.pos[i * 3 + 2] = (rand() - 0.5) * 0.4 * DEPTH * 0.3;
644      built.intensity[i] = 0.9;
645    }
646
647    A.fit(built.pos, n, 1);
648    return built;
649  };
650
651  /* =======================================
652     Model-organism figures
653     ======================================= */
654
655  /* A toy beside a real specimen: a tidy planted lattice on one side, the
656     traced animal on the other. */
657  const thesisFigure = (n) => {
658    const rand = A.makeRandom(13579);
659    const nodes = [];
660    const edges = [];
661    const SIDE = 4;
662
663    // The toy: a perfect little cube, entirely of your own making.
664    for (let i = 0; i < SIDE; i += 1) {
665      for (let j = 0; j < SIDE; j += 1) {
666        for (let k = 0; k < SIDE; k += 1) {
667          const index = nodes.length;
668          nodes.push([
669            -0.92 + (i / (SIDE - 1) - 0.5) * 0.5,
670            (j / (SIDE - 1) - 0.5) * 0.5,
671            (k / (SIDE - 1) - 0.5) * 0.5
672          ]);
673          if (i > 0) edges.push(index - SIDE * SIDE, index);
674          if (j > 0) edges.push(index - SIDE, index);
675          if (k > 0) edges.push(index - 1, index);
676        }
677      }
678    }
679
680    // The organism: irregular, and not yours.
681    const CORD = 20;
682    const cordStart = nodes.length;
683    for (let i = 0; i < CORD; i += 1) {
684      const t = i / (CORD - 1);
685      nodes.push([0.34 + t * 0.92, Math.sin(t * 2.6) * 0.14 + 0.08, A.gaussian(rand) * 0.05]);
686      if (i >
686 0) edges.push(cordStart + i - 1, cordStart + i);
687      if (i % 3 === 0 && i > 0) {
688        const side = i % 6 === 0 ? 1 : -1;
689        const g = nodes.length;
690        nodes.push([0.34 + t * 0.92, Math.sin(t * 2.6) * 0.14 - 0.22, side * 0.2]);
691        edges.push(cordStart + i, g);
692      }
693    }
694
695    const built = fromGraph(nodes, edges, n, {
696      rand,
697      jitter: 0.007,
698      nodeIntensity: (i) => (i < SIDE * SIDE * SIDE ? 0.36 : 1),
699      fill: 0.24
700    });
701
702    A.fit(built.pos, n, 1);
703    return built;
704  };
705
706  /* Everything traced: a dense graph with no unvisited part. */
707  const exhaustiveFigure = (n) => {
708    const rand = A.makeRandom(24680);
709    const NODES = 40;
710    const nodes = [];
711    const edges = [];
712
713    for (let i = 0; i < NODES; i += 1) {
714      const phi = Math.acos(1 - (2 * (i + 0.5)) / NODES);
715      const theta = Math.PI * (1 + Math.sqrt(5)) * (i + 0.5);
716      nodes.push([
717        Math.sin(phi) * Math.cos(theta) * 0.88,
718        Math.cos(phi) * 0.88,
719        Math.sin(phi) * Math.sin(theta) * 0.88
720      ]);
721    }
722    // Every pair. The point is that nothing is left out.
723    for (let i = 0; i < NODES; i += 1) {
724      for (let j = i + 1; j < NODES; j += 1) edges.push(i, j);
725    }
726
727    const built = fromGraph(nodes, edges, n, {
728      rand,
729      jitter: 0.004,
730      nodeIntensity: () => 1,
731      fill: 0.14
732    });
733
734    A.fit(built.pos, n, 1);
735    return built;
736  };
737
738  /* Two independent structures, and the correspondences that survive between
739     them: the model on one side, the experiment on the other. */
740  const groundTruthFigure = (n) => {
741    const rand = A.makeRandom(97531);
742    const PAIRS = 16;
743    const nodes = [];
744    const edges = [];
745
746    for (let i = 0; i < PAIRS; i += 1) {
747      const t = i / (PAIRS - 1) - 0.5;
748      const left = nodes.length;
749      nodes.push([-0.62, t * 1.65, A.gaussian(rand) * 0.1]);
750      const right = nodes.length;
751      nodes.push([0.62, t * 1.65 + A.gaussian(rand) * 0.09, A.gaussian(rand) * 0.1]);
752      // Only some correspondences hold; the rest are claims that failed.
753      if (rand() < 0.42) edges.push(left, right);
754    }
755
756    const built = fromGraph(nodes, edges, n, {
757      rand,
758      jitter: 0.006,
759      nodeIntensity: (i) => (i % 2 === 0 ? 0.9 : 0.55),
760      fill: 0.42
761    });
762
763    A.fit(built.pos, n, 1);
764    return built;
765  };
766
767  /* A tensor network: a chain of cores with bond legs and free indices, the
768     computation sitting in the structure rather than in the input. */
769  const tensorFigure = (n) => {
770    const rand = A.makeRandom(86420);
771    const CORES = 7;
772    const nodes = [];
773    const edges = [];
774    const cores = [];
775
776    for (let i = 0; i < CORES; i += 1) {
777      const x = (i / (CORES - 1) - 0.5) * 1.75;
778      const index = nodes.length;
779      cores.push(index);
780      nodes.push([x, 0, 0]);
781      if (i > 0) edges.push(cores[i - 1], index);
782
783      // Free indices, up and down.
784      const up = nodes.length;
785      nodes.push([x, 0.52, 0]);
786      edges.push(index, up);
787      const down = nodes.length;
788      nodes.push([x, -0.52, 0]);
789      edges.push(index, down);
790
791      // A second rank of bonds behind the chain.
792      const back = nodes.length;
793      nodes.push([x, 0, 0.46]);
794      edges.push(index, back);
795      if (i > 0) edges.push(back - 4, back);
796    }
797
798    const built = fromGraph(nodes, edges, n, {
799      rand,
800      jitter: 0.005,
801      nodeIntensity: (i) => (cores.indexOf(i) >= 0 ? 1 : 0.42),
802      fill: 0.3
803    });
804
805    A.fit(built.pos, n, 1);
806    return built;
807  };
808
809  // Two renderers can live on one page; the geometry is identical, so build it
810  // once and hand out the same set.
811  const cache = new Map();
812
813  window.BiodynFigures = {
814    build: (n) => {
815      if (!cache.has(n)) cache.set(n, buildAll(n));
816      return cache.get(n);
817    }
818  };
819
820  function buildAll(n) {
821    return ({
822      cases: [modelOrganism(n), cutawayAudit(n), extraction(n)],
823      organisms: {
824        thesis: thesisFigure(n),
825        exhaustive: exhaustiveFigure(n),
826        groundtruth: groundTruthFigure(n),
827        tensor: tensorFigure(n)
828      },
829      audits: {
830        gap: gapFigure(n),
831        recoverability: recoverabilityFigure(n),
832        standard: standardFigure(n),
833        coverage: coverageFigure(n)
834      },
835      applications: {
836        grn: grnFigure(n),
837        longevity: longevityFigure(n),
838        neuro: neuroFigure(n),
839        cell: cellFigure(n)
840      }
841    });
842  }
843})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.