← Back to Simulations

FRQTL SDK — Game Objects API

Engine-agnostic game objects for building physics experiments with the EGPT FRQTL engine.
No force calculations. No wave equations. The EGPT engine operates on three axioms: Time = 1 tick, Space = 1 pixel, Mass = pixel occupancy count. All physics — waves, interference, thermodynamics, circuits — emerges from local rules.

Quick Start

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);

EGPTUniverse

Constructor

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.

KeyTypeDefaultDescription
rectRectanglenullSimulation boundary (x, y, w, h). A leaf that leaves it is gone.
seednumber42PRNG seed — same seed, bit-identical run
fpsnumber60Target frame rate
iframeIntervalSecondsnumber1Seconds between iframe (higher-dimension) ticks; 1/60 = every tick
withInterQuantumCollisionsbooleanfalseEnable inter-frame collisions/merging (collision-born parent frames)
withBondingbooleanfalseBond 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
minMBFillRatenumber0.1Compression threshold for frame merging
wavelengthScalenumber1Global multiplier: wavelength = (capacity/mass) × scale
lowestDimensionnumber0Fundamental dimension number (leaf size = 2^n x 2^n)

Key Methods

MethodReturnsDescription
doTick()TickDataRun one simulation tick (Phase A-D pipeline)
registerLargeObject(spec)handleRegister a collidable boundary object
queryRect(left, top, w, h)ArrayQuery particles in a rectangular region
addDimension(layer)DimensionAdd/access a higher scale layer
There is no 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.

Frame Generation Modes

Top-down (frameGenMode:'topDown'): Phase B density clustering, brownian motion ON, no inter-particle collisions.
Ground-up (frameGenMode:'groundUp'): Phase C collision-based promotion, inter-particle collisions ON.

LargeObject

Engine-agnostic collidable boundary (wall, detector, wire segment). Uses handle-based API — calls universe.registerLargeObject(spec) internally.

Constructor

new LargeObject(universe, rect, collisionType, actions, name,
              reposition, observedDimensions, searchPadding, color, collectCollided)
ParameterTypeDescription
universeEGPTUniverseUniverse 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)
collisionTypestringHow particles collide (see CollisionTypes)
actionsstring[]What happens on collision (see CollisionActions)
namestringIdentifier for debugging/queries
repositionstringEject direction on collision (default 'closest')
observedDimensionsnumber[] | nullDimension(s) this object collides in; null = all
searchPaddingnumberCollision-search rect padding (default 1)
color[r,g,b]RGB color for rendering (default [120,120,120])
collectCollidedbooleanBuffer a FrameData copy per collided frame each tick (needed for a counting/detect wall)

Properties

PropertyDescription
.rectBounding rectangle
.colorRGB color array
.visibleWhether to render (default true)
.alphaOpacity (0-255)
.handleOpaque engine handle

QuantumEmitter

Emits leaf frames (quanta) into the universe. Supports auto-fire, manual fire, temperature-controlled emission rate.

Constructor

new QuantumEmitter(universe, source_rects, vx, vy, burst_size,
                 charge, auto_fire, random_direction, quantum_limit, delay, wrap, opts)
ParameterDefaultDescription
source_rectsArray of emission region rects
vx, vyInitial velocity (direction) of emitted particles
burst_size1Particles emitted per fire
chargenull0/1/'POSITIVE'/'NEGATIVE'/null (random) — back-compat; maps onto chargeMode
auto_firetrueEmit automatically each tick
random_directionfalseRandomize emission direction
quantum_limitInfinityMax quanta to emit
delay0Ticks between bursts
wrapfalseWrap emitted particles at canvas boundaries
opts.wavelengthnullPer-emitted-particle wavelength override (drives mass); null = engine default
opts.zMin, opts.zDepthnullUniform 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.

SphereEmitter

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();
OptionDefaultDescription
preset'spherical''spherical' (full ball, a sun) | 'laser' (collimated beam) | 'drum' (planar disk, no forward component) | 'particle-walk' (deterministic drift + transverse wave)
is2DfalseFixed at construction. true → velocities sampled as (vx,vy) disk-rejected (no z)
R24Velocity sampling range [-R,R] per axis and the direction-count/resolution knob
mode'ball''ball' (filled sphere of directions) | 'shell' (thin direction band)
emissionBoxSide0Side (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
innerR0Shell radius (px) when emissionShell:'surface'
burstSize40Leaves emitted per burst
emitEveryTicks6Ticks 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)
fundamentalDimension0Init-time dimension this emitter starts at (raises leaf capacity so wavelength gains leverage). Boot-level — reload to change
fundamentalWaveLength64Init-time wavelengthScale seed (the GLOBAL multiplier, distinct from the per-leaf wavelength above). Boot-level — reload to change

Live STUDIO-editable fields

Beyond the construction options above, the STUDIO Inspector exposes these fields on an already-placed SphereEmitter (captured/round-tripped by codegen):

FieldDefaultDescription
centercanvas centerEmission source position [x,y,z] in absolute canvas px — the captured position authority (edit live via the marker drag or setCenter)
renderColornullPer-leaf render color override [r,g,b] (render-intent only, zero physics); null = scheme-driven fullness→ROYGBIV
renderShapenullPer-leaf render shape override: 'ball' | 'box' | 'circle' | 'cube' | 'sphere'; null = the scene's default shape
orientationDeg0Emitter 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
leafDimensionfundamentalDimensionThe 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
emissionCapuncappedSelf-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)
gateCounterName 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.

Wall

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
OptionDefaultDescription
rect[left,top,w,h] (or the 6-element [left,top,z,w,h,d] 3D form), or left/top/w/h individually
collisionBehaviorREQUIREDThe behavior axis — see the Collision LEXICON table below. Supply this or a preset
presetmirror | brick | detector-screen | transparent-probe | wall — construction sugar expanding to the lexicon fields
name'wall'Display/debug identifier
colorRGB [r,g,b] render color
alphaRender opacity 0-255
visibletrueWhether the wall is drawn (render-only; collision stays active either way)
layernull (all)Dimension layer this wall is scoped to; null = every layer
shellShapeRender shape hint of the wall's visual shell: ball | box | circle | cube | sphere (render-only)

Key Methods

MethodDescription
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.

Rotation and 3D

FieldDefaultDescription
orientationDeg0The 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
pitchDeg0Up/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
depth0Read-only: the wall's z-slab depth in physics px (0 = a flat 2D wall). Set via the 6-element rect form, not directly
z0Read-only: the wall's z placement (near-face…far-face center) in physics px. Set via the 6-element rect form

Collision LEXICON — the behavior axis and its hooks

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'
});
FieldDefaultDescription
collisionBehaviorREQUIREDnothing (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)
decoherencefalseOnly 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
markTagOnly 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)
capacity64Only 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).

Box

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]
})
OptionDefaultDescription
left, top, width, heightInner box rect (physics px); walls extend outward from it
wallThickness10Wall 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]
slitOptional {wall:'right'|'left'|'top'|'bottom', height} gap in one wall

Returns an object with .walls (array of 4 LargeObjects) and .innerRect (inner bounds).

Tunnel

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.

OptionDefaultDescription
left, topBore (aperture) top-left corner, px
lengthTravel-axis extent of the corridor, px
boreCross-section gap between the two mirror faces, px (the clear aperture)
wallThickness10Thickness 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.
directedAnglefrom axisDirected 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.
redirectSpread90Directed mode: half-width of the fan, in degrees. 90 = a full 180° forward fan; 0 = a perfectly collimated re-emission along directedAngle.
polarSpread0Directed 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.
exitCollapsefalsePoint-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.
exitRecentertruePin 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.
exitPhaseResetfalseRe-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.
z03D passthrough: z placement of the corridor (only meaningful together with depth)
depth3D 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

OvenBox

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]
})
OptionDefaultDescription
left, top, width, heightInner box rect (physics px)
wallThickness10Wall thickness, px
temperature300Thermostat target quanta count (call setTemperature(n) to change live)
slitHeight60Radiation aperture height on the slit wall
slitWall'right'Which wall face carries the slit: 'right' | 'left' | 'top' | 'bottom'
color[120,120,120]Wall color

Temperature Control

// Change temperature dynamically
ovenBox.setTemperature(500);

// Get current temperature
var temp = ovenBox.getTemperature();

DetectorWall

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 }
ParameterDefaultDescription
canvasRectBounding rect of the simulation area, {w,h} or [l,t,w,h]
wallXcanvasRect.w - 20X position of the detector wall (physics px)
wallY0Y 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
wallHeightcanvasRect.hWall height (physics px)
wallWidth20Wall thickness (physics px)
wallName'detector_wall'Identifier used to key the collision histogram

GraphingDetectorWall

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
});
OptionDefaultDescription
binPx12Face bin size in physics pixels; bins are anchored at the face origin
refreshMs100Repaint throttle (ms); hit accumulation itself is un-throttled
heatBase0.35Opacity (0-1) of the ramp's zero-color wash under the bins
A wall's collided-frame buffer may be drained by exactly one consumer per tick — pointing both a LiveChart and a GraphingDetectorWall at the same wall throws. Choose one instrument per detector.

LiveChart

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);
OptionDefaultDescription
rectPlacement/size of the chart panel in physics pixels
sourceA 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'
orientation90 for detectorHistogram, else 00 = standard vertical bars; 90 = horizontal bars stacked along y (the GYM detector-histogram presentation, aligned with a vertical detector wall beside it). Live-editable
labelsource nameDataset label shown in the chart legend
refreshMs100Repaint throttle (ms); hit accumulation itself is un-throttled (no dropped counts)
visibletrueWhether the chart panel is rendered (render-only)
alphaRender opacity 0-255 (render-only)

CircuitProbe

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();
ParameterDefaultDescription
x, yProbe rectangle position (physics px)
sizeProbe rectangle width and height, px
nameIdentifier 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.

CircuitGate

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)
ParameterDefaultDescription
gateType'AND' | 'OR' | 'NOT' | 'XOR' | 'BUFFER'
x, yGate body center (physics px)
signalWidth6Height (horizontal gate) or width (vertical gate) of the wire channel
gateLength4Length of the gate body along the flow axis
horizontaltruetrue = left-to-right flow; false = top-to-bottom
options.thresholdHigh2Density average (quanta/tick over windowTicks) to call a control input HIGH
options.thresholdLow1Density average below which a HIGH input reverts to LOW (hysteresis); must be < thresholdHigh
options.windowTicks20Sliding-window length (ticks) for the density average
options.notEmissionRate2Burst 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

Setup factories configure complete experiments. Each takes (universe, options) and returns an object with the created game objects.

setupParticleWalk(universe, options)

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

setupWaveInterference(universe, options)

Two coherent radial sources producing interference patterns.

var result = setupWaveInterference(universe, {});
// result.sources.source1, result.sources.source2, result.largeObjects

setupBlackbody(universe, options)

Heated box with temperature-controlled emitters.

var result = setupBlackbody(universe, { temperature: 300 });
// result.largeObjects, result.box, result.temperatureControl

setupCircuitBasic(universe, options)

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()

setupHalfAdder(universe, options)

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)

Constants

CollisionTypes

ConstantDescription
NONENo native collision response (an op-program, if any, still runs)
BOUNCEElastic bounce (reflect velocity)
REFLECT_XReflect horizontal velocity
REFLECT_YReflect vertical velocity
REFLECT_XYReflect both velocity components
RANDOM_XRandomize horizontal velocity
RANDOM_YRandomize vertical velocity
RANDOM_XYRandomize velocity direction
MOMENTUM_BASEDCollision response based on momentum
WALLEject to the nearest face (a solid, non-bouncing wall)

CollisionActions

ConstantDescription
ABSORBRemove the particle on contact
BOUNCEElastic bounce action
BLOCKPrevent passage (eject-only, no velocity flip)
COUNTIncrement the object's hit counter
MARKWrite a provenance tag onto the colliding frame (e.g. which slit it passed through)
DETECTBuffer the collided frame for histogram readout (pairs with collect_collided)
LOGLog 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.

Charge

ConstantValue
Charge.POSITIVE0
Charge.NEGATIVE1

Rendering

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.

Bring your own renderer. The engine is renderer-agnostic. The shipped p5 and three.js providers (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.