app
Circuit Trace Generators
Public Made by Adomby adom
Two dependency-free, seeded circuit-background generators: a seamless torus tile with ambient or progressive reveal, and the v9 continuous field with obstacles, scroll reveal, and five ambient systems.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
function finiteNumber(value, fallback) {
const number = Number(value);
return Number.isFinite(number) ? number : fallback;
}
function batchBounds(traces) {
let minimum = Number.POSITIVE_INFINITY;
let maximum = Number.NEGATIVE_INFINITY;
for (const trace of traces) {
const minY = finiteNumber(trace?.minY, Number.NaN);
const maxY = finiteNumber(trace?.maxY, Number.NaN);
if (Number.isFinite(minY) && Number.isFinite(maxY)) {
minimum = Math.min(minimum, minY, maxY);
maximum = Math.max(maximum, minY, maxY);
continue;
}
for (const point of trace?.points || []) {
const y = Number(point?.[1]);
if (!Number.isFinite(y)) continue;
minimum = Math.min(minimum, y);
maximum = Math.max(maximum, y);
}
}
return [
Number.isFinite(minimum) ? minimum : 0,
Number.isFinite(maximum) ? maximum : 0,
];
}
/**
* Keeps pre-generated trace geometry off the SVG until its page is close
* enough to the viewport to participate in reveal ordering.
*
* Batches are always materialized in publication order. That preserves the
* reveal hash index and therefore the deterministic spawn order of an
* unstaged append stream.
*/
export function createPageTraceStager(options = {}) {
const materialize = options.materialize;
if (typeof materialize !== "function") {
throw new TypeError("materialize must be a function");
}
let pending = [];
let pendingHead = 0;
let materializedBatches = 0;
let materializedTraces = 0;
let bufferedTraces = 0;
let generatedTraces = 0;
let through = Number.NEGATIVE_INFINITY;
let destroyed = false;
function compact() {
if (!pendingHead) return;
pending = pending.slice(pendingHead);
pendingHead = 0;
}
function flush() {
if (destroyed) return 0;
let appended = 0;
while (pendingHead < pending.length) {
const batch = pending[pendingHead];
if (batch.minY > through) break;
pendingHead += 1;
bufferedTraces -= batch.traces.length;
materializedBatches += 1;
materializedTraces += batch.traces.length;
appended += batch.traces.length;
materialize(batch.traces, batch);
}
if (pendingHead > 64 && pendingHead > pending.length / 2) compact();
return appended;
}
const api = {
stage(traces) {
if (destroyed) return 0;
if (!Array.isArray(traces)) throw new TypeError("traces must be an array");
if (!traces.length) return 0;
const [minY, maxY] = batchBounds(traces);
const batch = Object.freeze({
traces,
minY,
maxY,
index: materializedBatches + pending.length - pendingHead,
});
pending.push(batch);
bufferedTraces += traces.length;
generatedTraces += traces.length;
return flush();
},
materializeThrough(worldY) {
if (destroyed) return 0;
through = Math.max(through, finiteNumber(worldY, through));
return flush();
},
clear() {
pending.length = 0;
pendingHead = 0;
materializedBatches = 0;
materializedTraces = 0;
bufferedTraces = 0;
generatedTraces = 0;
through = Number.NEGATIVE_INFINITY;
return api;
},
destroy() {
if (destroyed) return;
destroyed = true;
pending.length = 0;
pendingHead = 0;
bufferedTraces = 0;
},
get stats() {
return Object.freeze({
generatedTraces,
materializedTraces,
bufferedTraces,
materializedBatches,
bufferedBatches: pending.length - pendingHead,
materializedThroughWorldY: Number.isFinite(through) ? through : null,
});
},
};
return api;
}