Every EGPT experiment follows the same pattern: get an engine handle, create a universe, call a setup factory, run ticks, render. In Node (headless, no DOM):
const { EGPTEngine, setupDoubleSlit } = require('@descix/frqtl-sdk/node');
// 1. Get the engine handle (compiled WASM, same on browser and Node)
const handle = await EGPTEngine.create({ backend: 'wasm' });
// 2. Create a universe — EGPTUniverse.create(config) is the ONE construction authority
const universe = handle.EGPTUniverse.create({ rect: { left: 0, top: 0, w: 600, h: 600 }, seed: 42 });
// 3. Setup an experiment
const { detectorWall } = setupDoubleSlit(universe, { wavelength: 64 });
// 4. Run simulation ticks
const tickData = universe.doTick();
In the browser (with a rendered canvas) the construction call is the SAME one — EGPTUniverse.create(config) — reached off the global the engine boot installs. Hand each tick's data to a RendererProvider:
var universe = EGPTUniverse.create({ rect: { left: 0, top: 0, w: 600, h: 600 }, seed: 42 });
var result = setupParticleWalk(universe, {});
// each draw frame (activeRenderer = a RendererProvider — see Rendering below):
var tickData = universe.doTick();
activeRenderer.renderTick(tickData, result.largeObjects);
new EGPTUniverse(config) // ONE config bag — name only what you change
new EGPTUniverse({ rect: rect, seed: 42 });
Every key is optional and carries the default below, so a scene names only the settings it actually means to change. Unknown keys throw.
| Key | Type | Default | Description |
|---|---|---|---|
rect | Rectangle | null | Simulation boundary (x, y, w, h). A leaf that leaves it is gone. |
seed | number | 42 | PRNG seed — same seed, bit-identical run |
fps | number | 60 | Target frame rate |
iframeIntervalSeconds | number | 1 | Seconds between iframe (higher-dimension) ticks; 1/60 = every tick |
withInterQuantumCollisions | boolean | false | Enable inter-frame collisions/merging (collision-born parent frames) |
withBonding | boolean | false | Bond vs charge-flip on collision |
frameGenMode | 'groundUp' | 'topDown' | 'groundUp' | How PARENT frames are generated: 'groundUp' = by actual collisions between the smallest frames; 'topDown' = by how many frames an area can parent |
minMBFillRate | number | 0.1 | Compression threshold for frame merging |
wavelengthScale | number | 1 | Global multiplier: wavelength = (capacity/mass) × scale |
lowestDimension | number | 0 | Fundamental dimension number (leaf size = 2^n x 2^n) |
| Method | Returns | Description |
|---|---|---|
doTick() | TickData | Run one simulation tick (Phase A-D pipeline) |
registerLargeObject(spec) | handle | Register a collidable boundary object |
queryRect(left, top, w, h) | Array | Query particles in a rectangular region |
addDimension(layer) | Dimension | Add/access a higher scale layer |
registerEmitter / setEmitterActive / unregisterEmitter
method on the universe — an emitter (QuantumEmitter, SphereEmitter, ...) is
engine-agnostic sugar: it runs as a host tick-fn pushed onto universe.experimentTickFunctions,
not a native engine registration.
frameGenMode:'topDown'): Phase B density clustering, brownian motion ON, no inter-particle collisions.frameGenMode:'groundUp'): Phase C collision-based promotion, inter-particle collisions ON.
Engine-agnostic collidable boundary (wall, detector, wire segment). Uses handle-based API — calls universe.registerLargeObject(spec) internally.
new LargeObject(universe, rect, collisionType, actions, name,
reposition, observedDimensions, searchPadding, color, collectCollided)
| Parameter | Type | Description |
|---|---|---|
universe | EGPTUniverse | Universe instance to register with |
rect | [left,top,w,h] | [left,top,z,w,h,d] | Position and dimensions (6-element form for a 3D z-slab) |
collisionType | string | How particles collide (see CollisionTypes) |
actions | string[] | What happens on collision (see CollisionActions) |
name | string | Identifier for debugging/queries |
reposition | string | Eject direction on collision (default 'closest') |
observedDimensions | number[] | null | Dimension(s) this object collides in; null = all |
searchPadding | number | Collision-search rect padding (default 1) |
color | [r,g,b] | RGB color for rendering (default [120,120,120]) |
collectCollided | boolean | Buffer a FrameData copy per collided frame each tick (needed for a counting/detect wall) |
| Property | Description |
|---|---|
.rect | Bounding rectangle |
.color | RGB color array |
.visible | Whether to render (default true) |
.alpha | Opacity (0-255) |
.handle | Opaque engine handle |
Emits leaf frames (quanta) into the universe. Supports auto-fire, manual fire, temperature-controlled emission rate.
new QuantumEmitter(universe, source_rects, vx, vy, burst_size,
charge, auto_fire, random_direction, quantum_limit, delay, wrap, opts)
| Parameter | Default | Description |
|---|---|---|
source_rects | — | Array of emission region rects |
vx, vy | — | Initial velocity (direction) of emitted particles |
burst_size | 1 | Particles emitted per fire |
charge | null | 0/1/'POSITIVE'/'NEGATIVE'/null (random) — back-compat; maps onto chargeMode |
auto_fire | true | Emit automatically each tick |
random_direction | false | Randomize emission direction |
quantum_limit | Infinity | Max quanta to emit |
delay | 0 | Ticks between bursts |
wrap | false | Wrap emitted particles at canvas boundaries |
opts.wavelength | null | Per-emitted-particle wavelength override (drives mass); null = engine default |
opts.zMin, opts.zDepth | null | Uniform z-spread [zMin, zMin+zDepth) for each emitted leaf (3D only) |
Set chargeMode ('random' | 'positive' | 'negative' | 'both') and colorOverride after construction to drive charge/color live.
Object-constructor facade for the uniform-direction emitter family — a "sun" (spherical), "laser" (collimated beam), "drum" (planar disk of directions), or "particle-walk" (deterministic drift + transverse wave) source, all drawn from the SAME ball-rejected velocity sampler. This is the palette-exposed, position-holding way to build a radial or directional light/particle source.
var sun = new SphereEmitter([x, y], { preset: 'spherical', is2D: true });
sun.addTo(universe);
sun.setCenter([x2, y2]); // reposition live
sun.setWavelength(200); // dial the next leaf's mass
sun.dispose();
| Option | Default | Description |
|---|---|---|
preset | 'spherical' | 'spherical' (full ball, a sun) | 'laser' (collimated beam) | 'drum' (planar disk, no forward component) | 'particle-walk' (deterministic drift + transverse wave) |
is2D | false | Fixed at construction. true → velocities sampled as (vx,vy) disk-rejected (no z) |
R | 24 | Velocity sampling range [-R,R] per axis and the direction-count/resolution knob |
mode | 'ball' | 'ball' (filled sphere of directions) | 'shell' (thin direction band) |
emissionBoxSide | 0 | Side (px) of the cube emission region leaves are placed within; 0 = point source |
emissionShell | 'volume' | 'volume' | 'surface' — where within the emission region leaves are placed |
innerR | 0 | Shell radius (px) when emissionShell:'surface' |
burstSize | 40 | Leaves emitted per burst |
emitEveryTicks | 6 | Ticks between bursts |
chargeMode | 'positive' | 'positive' | 'negative' | 'random' — per-leaf charge for interference braiding |
wavelength | (engine default) | Per-leaf wavelength arg forwarded to each emitted leaf (drives its mass/color) |
fundamentalDimension | 0 | Init-time dimension this emitter starts at (raises leaf capacity so wavelength gains leverage). Boot-level — reload to change |
fundamentalWaveLength | 64 | Init-time wavelengthScale seed (the GLOBAL multiplier, distinct from the per-leaf wavelength above). Boot-level — reload to change |
Beyond the construction options above, the STUDIO Inspector exposes these fields on an already-placed SphereEmitter (captured/round-tripped by codegen):
| Field | Default | Description |
|---|---|---|
center | canvas center | Emission source position [x,y,z] in absolute canvas px — the captured position authority (edit live via the marker drag or setCenter) |
renderColor | null | Per-leaf render color override [r,g,b] (render-intent only, zero physics); null = scheme-driven fullness→ROYGBIV |
renderShape | null | Per-leaf render shape override: 'ball' | 'box' | 'circle' | 'cube' | 'sphere'; null = the scene's default shape |
orientationDeg | 0 | Emitter orientation, degrees. The velocity sampler rotates each emitted (vx,vy) by this angle (affects the NEXT leaf) — spinning a constrained/laser emitter sweeps its beam (a pulsar); an omni emitter is unaffected |
leafDimension | fundamentalDimension | The per-emitter dimension leaves are born at (live, host-side addDimension). A value below the current fundamental self-lowers the universe floor — a one-way ratchet |
emissionCap | uncapped | Self-throttle target: the emitter deactivates when the population measured by gateCounter reaches this value, reactivates below it (the blackbody-thermostat pattern applied to an emitter) |
gateCounter | — | Name of a counting Wall (collisionBehavior:'nothing' + the count hook) whose live count is emissionCap's population authority. Required once emissionCap is set |
Every other control method the underlying factory exposes (getCenter, setPreset, setR, setBurstSize, setChargeMode, setEmissionShell, setInnerR, setEmissionBoxSide, setRenderColor, …) is auto-forwarded onto the instance — the object is a thin, zero-physics naming/ergonomics face (canonical-bench SU1).
rect (a write-only drag-handle adapter over center) and emissionBox (a derived read-out of emissionBoxSide) are internal round-trip plumbing, not separate knobs — edit position via center/the marker drag and box size via emissionBoxSide.
The first-class wall primitive — a thin, named facade over one engine-registered LargeObject whose collision response is the Collision LEXICON: a collisionBehavior axis plus composable statistics hooks. collisionBehavior (or a preset) is REQUIRED — a wall must say what it does.
var barrier = new Wall(universe, { rect: [200, 0, 8, 120], collisionBehavior: 'block' });
barrier.setV2Prop('collisionBehavior', 'bounce'); // recompile the running behavior live
var screen = new Wall(universe, { rect: [400, 0, 20, 400], preset: 'detector-screen' });
screen.getCollisionHistogram(tickData); // y -> count on the counting screen
| Option | Default | Description |
|---|---|---|
rect | — | [left,top,w,h] (or the 6-element [left,top,z,w,h,d] 3D form), or left/top/w/h individually |
collisionBehavior | REQUIRED | The behavior axis — see the Collision LEXICON table below. Supply this or a preset |
preset | — | mirror | brick | detector-screen | transparent-probe | wall — construction sugar expanding to the lexicon fields |
name | 'wall' | Display/debug identifier |
color | — | RGB [r,g,b] render color |
alpha | — | Render opacity 0-255 |
visible | true | Whether the wall is drawn (render-only; collision stays active either way) |
layer | null (all) | Dimension layer this wall is scoped to; null = every layer |
shellShape | — | Render shape hint of the wall's visual shell: ball | box | circle | cube | sphere (render-only) |
| Method | Description |
|---|---|
setV2Prop(prop, value) | Retarget a lexicon field live (collisionBehavior / bounceDirection / decoherence / hooks / markTag / countFilter / capacity / orientationDeg / pitchDeg) — recompiles the running op-program |
getCollisionHistogram(tickData) | For a wall carrying the count hook: accumulate and return {combined, grid, total} hit counts |
getLiveCount(tickData) | For a wall with the count hook: the number of frames colliding this tick (live population) |
resetHistogram() | Clear the accumulated histogram |
dispose() | Tear down (unregisters any host tick-fn, disposes the inner LargeObject) |
setOrientation(deg) / getOrientation() | Live re-aim (see orientationDeg below) |
setPitch(deg) | Live tilt (see pitchDeg below; throws on a 2D wall) |
getAbsorbStats() | For an absorb-behavior wall: {stored, absorbedTotal, radiatedTotal}, the real backing-frame mass ledger |
DetectorWall and GraphingDetectorWall (below) both build on this primitive.
| Field | Default | Description |
|---|---|---|
orientationDeg | 0 | The mirror SURFACE angle, degrees. Live — recompiles the op-program: a reflect/bounce wall bakes the standard-optics 2θ deflection law (45° turns a +x beam to +y); a random-bounce wall re-aims its scatter cone |
pitchDeg | 0 | Up/down tilt (-90..90°) about the plate's own long axis — the second mirror rotation axis, 3D walls only (visible only when depth is set). 0 = the flat in-plane mirror |
depth | 0 | Read-only: the wall's z-slab depth in physics px (0 = a flat 2D wall). Set via the 6-element rect form, not directly |
z | 0 | Read-only: the wall's z placement (near-face…far-face center) in physics px. Set via the 6-element rect form |
The behavior axis is decomposed from the composable statistics hooks, so combinations like "count collisions without destroying the particle" are directly expressible:
var mirror = new Wall(universe, {
rect: [200, 0, 8, 120],
collisionBehavior: 'bounce', bounceDirection: 'reflect'
});
var countingScreen = new Wall(universe, {
rect: [400, 0, 20, 400],
collisionBehavior: 'destroy', hooks: ['count'], countFilter: 'any'
});
| Field | Default | Description |
|---|---|---|
collisionBehavior | REQUIRED | nothing (pass-through) | block (STOP the particle at the surface: it is repositioned to the closest position outside the wall and keeps its velocity, so it parks at the face rather than turning around) | bounce (see bounceDirection) | absorb (tally the collider's mass into the wall's REAL backing frame + radiate, conserving) | destroy (remove the particle). Hooks compose with any behavior |
bounceDirection | 'reflect' | Only shown for bounce: reflect = mirror (invert velocity on the axis of incidence) | random = redraw the exit direction (omni, or constrained to a cone about the wall's INITIAL orientation) |
decoherence | false | Only shown for a random bounce: also reset the wave phase + re-roll the charge sign on bounce (an independent toggle from the direction randomization) |
hooks | [] | Composable statistics hooks (enum-set, any behavior): mark = stamp a user string on each collided frame | count = tally collisions (filterable by mark) | recolor = stamp the wall's own drawn color onto each collided frame's render-intent data |
markTag | — | Only shown when hooks includes mark: the string MARK stamps into each collided frame |
countFilter | 'any' | Only shown when hooks includes count: restrict COUNT to frames carrying a specific markTag ('any' = count all) |
capacity | 64 | Only shown for absorb: the ABSORB mass ceiling (snapped to the backing leaf's level-derived capacity) AND the 1/capacity per-tick radiation probability |
Named presets expand to this v2 field set at construction (new Wall(universe, { preset: 'mirror', rect: [...] })): mirror (bounce+reflect), brick (bounce+random, no decoherence), detector-screen (destroy + count, any filter — a counting screen), transparent-probe (pass-through + mark + count — measure without disturbing), wall (block — a solid barrier that stops particles at its surface).
Rectangular enclosure made of four LargeObject walls. Particles bounce inside.
new Box(universe, {
left: 60, top: 60, width: 80, height: 80,
wallThickness: 10, collisionType: 'BOUNCE', actions: [], color: [120,120,120]
})
| Option | Default | Description |
|---|---|---|
left, top, width, height | — | Inner box rect (physics px); walls extend outward from it |
wallThickness | 10 | Wall thickness, px |
collisionType | 'BOUNCE' | Collision type applied to all walls |
actions | [] | Collision actions applied to all walls |
color | [120,120,120] | Wall color [r,g,b] |
slit | — | Optional {wall:'right'|'left'|'top'|'bottom', height} gap in one wall |
Returns an object with .walls (array of 4 LargeObjects) and .innerRect (inner bounds).
Two parallel Walls forming a corridor. In the default mirror mode it is the waveguide/light-pipe primitive: a beam travelling down the bore reflects off the two facing mirror walls and stays collimated inside the channel. Pure composition: zero engine, zero new physics — every collision effect is realized by the two walls' own compiled behavior.
var t = new Tunnel(universe, { left: 96, top: 233, length: 64, bore: 31 });
t.walls; // [ Wall{bounce}, Wall{bounce} ] — the two mirror surfaces
t.innerRect; // [96, 233, 64, 31] — the clear bore the beam passes through
Directed mode. mode:'directed' turns the corridor into a semi-circular emitter instead of a mirror: a wall hit ejects the quantum back into the bore and re-emits it in a uniformly random direction within 180° of the corridor axis. That is what makes a slit spread a beam rather than merely pass it, so it is the mode the double-slit scene uses for its slit channels. The exit angle is drawn between ticks with real trigonometry, which is what keeps the fan flat — an in-collision random redraw is diagonal-weighted (it peaks near ±45°) rather than angle-uniform.
var slit = new Tunnel(universe, {
left: 96, top: 233, length: 64, bore: 31,
axis: 'x', mode: 'directed' // fan aims +x (from `axis`), half-width 90° = a full 180°
});
runner.addTickListener(function (tickData) { slit.applyRedirectIfActive(tickData); });
The re-emission is a between-ticks step, so it needs one call per tick with that tick's tickData. applyRedirectIfActive self-gates on the mode, so it is safe to call for every tunnel unconditionally; when a Tunnel is created through the object factory this wiring is done for you.
| Option | Default | Description |
|---|---|---|
left, top | — | Bore (aperture) top-left corner, px |
length | — | Travel-axis extent of the corridor, px |
bore | — | Cross-section gap between the two mirror faces, px (the clear aperture) |
wallThickness | 10 | Thickness of each mirror wall, px |
axis | 'x' | 'x' → horizontal corridor (mirrors above/below); 'y' → vertical corridor (mirrors left/right) |
mode | 'mirror' | 'mirror' = guarded-bounce waveguide (collimating). 'directed' = each wall hit re-emits into a random forward direction within 180° of the corridor axis. Set at construction (the walls are registered differently), so rebuild to change it. |
directedAngle | from axis | Directed mode: centre of the forward fan, in degrees. Defaults to the corridor's own travel direction (0 for an x corridor, 90 for a y one). Set 180/270 for a corridor fed from the far end. |
redirectSpread | 90 | Directed mode: half-width of the fan, in degrees. 90 = a full 180° forward fan; 0 = a perfectly collimated re-emission along directedAngle. |
polarSpread | 0 | Directed mode: half-width of the out-of-plane fan, in degrees. 0 (default, even for a 3D corridor) keeps the fan in the XY plane. A non-zero value draws the polar angle uniformly, which over-weights the pole — it is polar-uniform, not solid-angle-uniform. |
exitCollapse | false | Point-exit gate. Adds a third Wall-backed collision region spanning the last 2 columns of the bore at the output mouth; every crossing quantum is pinned to the bore mid-line, so the mouth radiates as a one-pixel point source. Composes with either mode. Set at construction. Requires withInterQuantumCollisions:false — it throws otherwise, because with collisions on the collapse pixel serializes one quantum per tick and kills the contended ones. |
exitRecenter | true | Pin every quantum crossing the exit gate to the bore mid-line — the point-source collapse. Turning it off leaves the exit spatially unchanged, which separates the question "does the point source matter?" from "does coherence matter?" (exitPhaseReset). With both off the gate carries no program at all and remains only as an inert collector. Live via setExitRecenter(); requires exitCollapse. |
exitPhaseReset | false | Re-phase-lock the ensemble at the exit gate (adds local_time = 0 to the gate's program). Whether the fan bands without it is wavelength-dependent: at long effective λ the mouth is already nearly phase-aligned and bands appear with this off; at short λ the mouth is phase-flat and the fan reads smooth until you turn this on. Phase only — it never redraws charge. Live via setExitPhaseReset(); requires exitCollapse. |
z | 0 | 3D passthrough: z placement of the corridor (only meaningful together with depth) |
depth | — | 3D passthrough: when given, each mirror Wall is built as a real z-slab (six-face reflect); omitted = a flat 2D-in-3D corridor |
color | [90,120,200] | RGB render color for both mirrors |
Temperature-controlled box — a Box with inward-firing wall emitters (op-programs; bit-identical to native bounce) and a thermostat holding the cavity at a target quanta count. Used for blackbody radiation experiments.
new OvenBox(universe, {
left: 60, top: 40, width: 100, height: 120,
wallThickness: 10, temperature: 300, slitHeight: 60, color: [120,120,120]
})
| Option | Default | Description |
|---|---|---|
left, top, width, height | — | Inner box rect (physics px) |
wallThickness | 10 | Wall thickness, px |
temperature | 300 | Thermostat target quanta count (call setTemperature(n) to change live) |
slitHeight | 60 | Radiation aperture height on the slit wall |
slitWall | 'right' | Which wall face carries the slit: 'right' | 'left' | 'top' | 'bottom' |
color | [120,120,120] | Wall color |
// Change temperature dynamically
ovenBox.setTemperature(500);
// Get current temperature
var temp = ovenBox.getTemperature();
Counting/absorbing detector screen — a thin wrapper over a Wall{detect} that additionally splits its running histogram by slit provenance. Used for double-slit-style fringe measurement.
new DetectorWall(universe, canvasRect, wallX, wallY, wallCollisionType,
wallColor, wallHeight, wallWidth, wallName)
// Read the accumulated histogram
var hist = detectorWall.getCollisionHistogram(tickData);
// { combined: Map<y,count>, slit1: Map<y,count>, slit2: Map<y,count>, total }
| Parameter | Default | Description |
|---|---|---|
canvasRect | — | Bounding rect of the simulation area, {w,h} or [l,t,w,h] |
wallX | canvasRect.w - 20 | X position of the detector wall (physics px) |
wallY | 0 | Y position (physics px) |
wallCollisionType | 'NONE' | Underlying engine collision type — leave at the default; absorption is handled by the ABSORB action, not the collision type |
wallColor | [120,120,120] | RGB render color |
wallHeight | canvasRect.h | Wall height (physics px) |
wallWidth | 20 | Wall thickness (physics px) |
wallName | 'detector_wall' | Identifier used to key the collision histogram |
A detector screen that paints its own hit heatmap directly onto its face — a chart-free, spatially registered instrument (a hot cell sits exactly where the counted collisions occurred). Extends Wall, so every Wall option and method applies; defaults to the counting (detect-equivalent) behavior.
var screen = new GraphingDetectorWall(universe, {
rect: [236, 0, 20, 600], name: 'screen', binPx: 12
});
| Option | Default | Description |
|---|---|---|
binPx | 12 | Face bin size in physics pixels; bins are anchored at the face origin |
refreshMs | 100 | Repaint throttle (ms); hit accumulation itself is un-throttled |
heatBase | 0.35 | Opacity (0-1) of the ramp's zero-color wash under the bins |
LiveChart and a GraphingDetectorWall at the same wall throws. Choose one instrument per detector.
An in-scene live chart panel that plots a counting wall's running histogram as the sim runs — a render-only object (no collision, zero physics) built through the object factory rather than a standalone constructor. The chart data model ships with the engine layer; drawing the panel to a canvas is done by the host chart-wiring (the STUDIO/IDE layer, Chart.js-backed) that composites it into the scene as a texture.
// Via the declarative object factory (STUDIO / a host that provides ObjectFactory):
ObjectFactory.create('LiveChart', {
rect: [420, 40, 200, 140],
source: 'detector', // name of a detect/count Wall
projector: 'detectorHistogram'
}, ctx);
| Option | Default | Description |
|---|---|---|
rect | — | Placement/size of the chart panel in physics pixels |
source | — | A counting/absorbing Wall handle, or its name — a source lacking the projector's read method fails loud |
projector | 'detectorHistogram' | detectorHistogram (y→count fringes) | absorberStats | liveCount | detectorSurface |
series | 'bar' | 'bar' | 'line' |
orientation | 90 for detectorHistogram, else 0 | 0 = standard vertical bars; 90 = horizontal bars stacked along y (the GYM detector-histogram presentation, aligned with a vertical detector wall beside it). Live-editable |
label | source name | Dataset label shown in the chart legend |
refreshMs | 100 | Repaint throttle (ms); hit accumulation itself is un-throttled (no dropped counts) |
visible | true | Whether the chart panel is rendered (render-only) |
alpha | — | Render opacity 0-255 (render-only) |
Non-intrusive density measurement. Queries the region via universe.queryRect() each tick without absorbing or otherwise affecting particles.
new CircuitProbe(universe, x, y, size, name)
// Read current flow rate (sliding-window average over probe.windowSize ticks)
var rate = probe.getFlowRate();
| Parameter | Default | Description |
|---|---|---|
x, y | — | Probe rectangle position (physics px) |
size | — | Probe rectangle width and height, px |
name | — | Identifier for the probe |
probe.windowSize (default 10) sets the sliding-window length used by getFlowRate(). Registers a visual-only NONE-collision marker so the probe region shows up in the renderer.
Density-activated gate: a boundary that blocks (WALL) or opens (NONE) depending on the sampled particle density in one or more control regions — enabling boolean logic from particle flow.
new CircuitGate(universe, gateType, x, y, signalWidth, gateLength, horizontal, options)
| Parameter | Default | Description |
|---|---|---|
gateType | — | 'AND' | 'OR' | 'NOT' | 'XOR' | 'BUFFER' |
x, y | — | Gate body center (physics px) |
signalWidth | 6 | Height (horizontal gate) or width (vertical gate) of the wire channel |
gateLength | 4 | Length of the gate body along the flow axis |
horizontal | true | true = left-to-right flow; false = top-to-bottom |
options.thresholdHigh | 2 | Density average (quanta/tick over windowTicks) to call a control input HIGH |
options.thresholdLow | 1 | Density average below which a HIGH input reverts to LOW (hysteresis); must be < thresholdHigh |
options.windowTicks | 20 | Sliding-window length (ticks) for the density average |
options.notEmissionRate | 2 | Burst size of the internal emitter a NOT gate creates (unused by other gate types) |
options.fundamentalDim is currently unused (reserved for multi-dimension addressing) — it has no observable effect.
Setup factories configure complete experiments. Each takes (universe, options) and returns an object with the created game objects.
Wave-particle duality demonstration. A single emitter fires particles that exhibit both random-walk and wave behavior.
var result = setupParticleWalk(universe, {});
// result.largeObjects — walls for rendering
// result.emitter — the QuantumEmitter
Two coherent radial sources producing interference patterns.
var result = setupWaveInterference(universe, {});
// result.sources.source1, result.sources.source2, result.largeObjects
Heated box with temperature-controlled emitters.
var result = setupBlackbody(universe, { temperature: 300 });
// result.largeObjects, result.box, result.temperatureControl
Series resistor circuit — battery, wire, resistor, ground.
var result = setupCircuitBasic(universe, {});
// result.battery, result.probes.before_resistor, result.probes.after_resistor
// result.setBatteryVoltage(v), result.setBatteryAmperage(amp), result.getCircuitState()
Half-adder — SUM = A XOR B, CARRY = A AND B — built from two LogicGates,
straight Trace input rails and grounded Trace readout stubs.
Each logical input drives TWO independent generators at the full rail (control-plane
fan-out); the current is never split.
var result = setupHalfAdder(universe, { railVoltage: 6 });
// result.gates.SUM, result.gates.CARRY, result.readouts.SUM, result.readouts.CARRY
// result.setInputA(bool), result.setInputB(bool), result.getState()
// result.getLayout(), result.getSettleBudget(), result.setRailVoltage(v)
// Drive the per-tick fan-out with EXACTLY ONE of:
// result.attachToRunner(runner) — or — result.onTick(tickData)
| Constant | Description |
|---|---|
NONE | No native collision response (an op-program, if any, still runs) |
BOUNCE | Elastic bounce (reflect velocity) |
REFLECT_X | Reflect horizontal velocity |
REFLECT_Y | Reflect vertical velocity |
REFLECT_XY | Reflect both velocity components |
RANDOM_X | Randomize horizontal velocity |
RANDOM_Y | Randomize vertical velocity |
RANDOM_XY | Randomize velocity direction |
MOMENTUM_BASED | Collision response based on momentum |
WALL | Eject to the nearest face (a solid, non-bouncing wall) |
| Constant | Description |
|---|---|
ABSORB | Remove the particle on contact |
BOUNCE | Elastic bounce action |
BLOCK | Prevent passage (eject-only, no velocity flip) |
COUNT | Increment the object's hit counter |
MARK | Write a provenance tag onto the colliding frame (e.g. which slit it passed through) |
DETECT | Buffer the collided frame for histogram readout (pairs with collect_collided) |
LOG | Log the collision (debugging) |
Render color on collision (the old RECOLOR action) is not an engine concept — the engine
carries no color. Set it via the render-intent string_map (sugar-layer) instead.
| Constant | Value |
|---|---|
Charge.POSITIVE | 0 |
Charge.NEGATIVE | 1 |
The engine produces pure TickData from doTick(); drawing is a separate step, done by a RendererProvider — a plain object exposing renderTick(tickData, largeObjects, options) plus lifecycle (init/setSize/start/stop/destroy) and a filter/removeFilter post-process pair. Two providers ship with the engine — p5 (2D) and three.js (3D) — behind this one frozen interface.
// In your draw loop, once a provider is resolved (see below):
var tickData = universe.doTick();
if (tickData) {
activeRenderer.renderTick(tickData, largeObjects);
}
TickData contains per-dimension arrays of frame positions, colors, and sizes. A provider draws each frame per its render-intent (color/shape/mesh, carried opaquely on the frame's data) and each LargeObject as a filled rect with optional alpha.
js/compat/renderer/providers/ in this package) are two examples behind one frozen interface —
write your own provider implementing the same init/setSize/renderTick/filter/destroy
surface to render to Canvas 2D, WebGL, or any other target.
Copyright 2023-2026 Essam Abadir. Licensed under the DeSciX Community License.
@descix/frqtl-sdk — No force calculations. Physics emerges.