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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
import { generateCircuitField } from "./circuit-geometry-v9.js?geometry=1";
import { createStreamingCircuitField } from "./circuit-field-stream.js";
function finiteNumber(value, fallback) {
const number = Number(value);
return Number.isFinite(number) ? number : fallback;
}
function traceBounds(points) {
let minimum = Number.POSITIVE_INFINITY;
let maximum = Number.NEGATIVE_INFINITY;
for (const point of points) {
minimum = Math.min(minimum, point[1]);
maximum = Math.max(maximum, point[1]);
}
return [minimum, maximum];
}
function initialTraceAdapter(trace, index) {
const [minY, maxY] = traceBounds(trace.points);
return Object.freeze({
...trace,
id: `initial:${index}`,
chunkId: 0,
ownerChunk: 0,
minY,
maxY,
});
}
function continuationTraceAdapter(trace, translateY) {
return Object.freeze({
...trace,
id: `continuation:${trace.id}`,
chunkId: trace.chunkId + 1,
ownerChunk: trace.ownerChunk + 1,
points: trace.points.map(([x, y]) => [x, y + translateY]),
minY: trace.minY + translateY,
maxY: trace.maxY + translateY,
});
}
/**
* Combines the canonical finite generator with one persistent continuation.
*
* The first ensure publishes the unchanged finite geometry through adapter
* copies. Later batches come from one deterministic streaming field whose
* fixed chunk height equals the finite page's normalized height.
*/
export function createHybridCircuitField(options = {}) {
let config;
let initialField;
let initialBatch;
let continuation;
let unsubscribeContinuation;
let continuationTranslateY;
let generationTolerance;
let retained;
let callbacks;
let initialPublished;
let totalPruned;
let destroyed = false;
function assertLive() {
if (destroyed) throw new Error("Hybrid circuit field has been destroyed");
}
function notify(batch) {
for (const callback of callbacks) callback(batch, api);
}
function initialize(nextOptions) {
unsubscribeContinuation?.();
continuation?.destroy();
config = { ...nextOptions };
const requestedWidth = finiteNumber(config.width, 900);
const requestedInitialPageHeight = finiteNumber(
config.initialPageHeight,
finiteNumber(config.height, 450),
);
initialField = generateCircuitField({
...config,
width: requestedWidth,
height: requestedInitialPageHeight,
});
const pitch = initialField.height / Math.max(1, initialField.rows - 1);
const continuationRows = Math.max(1, initialField.rows - 1);
continuationTranslateY = initialField.height - pitch;
generationTolerance = Math.max(1e-9, pitch * 1e-7);
initialBatch = Object.freeze(
initialField.traces.map(initialTraceAdapter),
);
retained = [];
initialPublished = false;
totalPruned = 0;
continuation = createStreamingCircuitField({
...config,
width: requestedWidth,
seed: `${config.seed ?? "nitrogen"}:continuation`,
chunkRows: continuationRows,
});
unsubscribeContinuation = continuation.onAppend((traces) => {
const batch = Object.freeze(
traces.map((trace) => continuationTraceAdapter(trace, continuationTranslateY)),
);
retained.push(...batch);
notify(batch);
});
}
function publishInitial() {
if (initialPublished) return [];
initialPublished = true;
retained.push(...initialBatch);
notify(initialBatch);
return initialBatch;
}
const api = {
ensureThrough(worldY) {
assertLive();
const target = Math.max(0, finiteNumber(worldY, 0));
const appended = [...publishInitial()];
const continuationTarget = Math.max(0, target - initialField.height);
const tolerantTarget = Math.max(0, continuationTarget - generationTolerance);
if (tolerantTarget > continuation.generatedThrough) {
const before = retained.length;
continuation.ensureThrough(tolerantTarget);
appended.push(...retained.slice(before));
}
return appended;
},
onAppend(callback) {
assertLive();
if (typeof callback !== "function") throw new TypeError("callback must be a function");
callbacks.add(callback);
return () => callbacks.delete(callback);
},
pruneBefore(worldY) {
assertLive();
const numericBoundary = Number(worldY);
const boundary = Number.isNaN(numericBoundary) ? 0 : numericBoundary;
const removed = [];
let writeIndex = 0;
for (const trace of retained) {
if (trace.maxY < boundary) {
removed.push(trace.id);
continue;
}
retained[writeIndex] = trace;
writeIndex += 1;
}
retained.length = writeIndex;
continuation.pruneBefore(boundary - continuationTranslateY);
totalPruned += removed.length;
return removed;
},
reset(nextOptions = {}) {
assertLive();
initialize({ ...config, ...nextOptions });
return api;
},
destroy() {
if (destroyed) return;
destroyed = true;
unsubscribeContinuation?.();
continuation?.destroy();
callbacks.clear();
retained = [];
},
get width() {
return initialField.width;
},
get height() {
return initialField.height + continuation.generatedThrough;
},
get generatedThrough() {
return initialField.height + continuation.generatedThrough;
},
get frontier() {
return initialField.height + continuation.generatedThrough;
},
get traces() {
return retained;
},
get retainedTraces() {
return retained;
},
get initialField() {
return initialField;
},
get stats() {
const streamStats = continuation.stats;
const initialTraceCount = initialField.traces.length;
return Object.freeze({
...streamStats,
chunks: streamStats.chunks + 1,
columns: initialField.columns,
chunkRows: initialField.rows - 1,
pitch: initialField.height / Math.max(1, initialField.rows - 1),
width: initialField.width,
generatedThrough: initialField.height + continuation.generatedThrough,
totalPublished: (initialPublished ? initialTraceCount : 0)
+ streamStats.totalPublished,
retainedTraces: retained.length,
totalPruned,
initialPublished,
initialPageHeight: initialField.height,
initialPageRows: initialField.rows,
initialPageTraceCount: initialTraceCount,
});
},
};
callbacks = new Set();
initialize(options);
return api;
}