Skip to content

Architecture

Architecture overview and core concepts for the Krestianstvo Wavefront Evaluator.

Live demo: wavefront.krestianstvo.org
Source code: github.com/NikolaySuslov/krestianstvo-wavefront-evaluator


The Wavefront Evaluator isn’t just a clever way to sync avatars; it is deeply rooted in computational physics and hardware architecture. It mimics how information naturally propagates through space-time.

The Wavefront Evaluator is essentially a “physics engine for information”. It replaces the “linear list” of standard programming with the laws of Classical Mechanics and Wave Propagation.

Wavefront Propagation. Every point on a wavefront acts as a source of secondary waves. Every node that receives a message becomes a “source” that can generate new messages (waves) for other nodes.

Causality and the Light Cone. Each node has a local queue. A message can only affect a future state (either in the next macro-tick or a later micro-tick). This preserves causality — the effect never happens before the cause.

ConceptPhysics EquivalentWavefront Implementation
PulseUniversal TimeThe Reflector Heartbeat
Node QueueLocal Particle StateW.reduce local _Q
Micro-tickParticle InteractionThe “Drain” / Feedback loop
StabilityThermal EquilibriumWhen all queues are empty

The system formula:

S(t+1) = Stability ( Drain ( Pulse(t) + S(t) ) )

Where S = State of the Universe, Pulse = The “Energy” injected by the Reflector, Drain = The “Work” performed by the nodes, Stability = The “Lowest Energy State.”


From Virtual Machine to Wavefront Evaluator

Section titled “From Virtual Machine to Wavefront Evaluator”
DimensionKrestianstvo VMKrestianstvo Wavefront Evaluator
Message queueCentralised — one shared queue per worldDecentralised — each node owns its own local queue
Time authorityVM clock drives all nodes uniformlyTwo-layer time: shared logical pulse + local micro-tick
CausalityEnforced by queue ordering at VM levelEmerges from wavefront propagation across node dependencies
Sub-step executionAsync, RAF-drivenSynchronous drain loop — sub-ticks are fractions of a logical tick
Late-join / desync recoveryManual snapshot and replayWarp mechanism — local clock-advance to catch up
Node communicationVM routes messages between nodes centrallyctx.send() writes to a per-world outbox; nodes pull on next evaluate
Stability detectionVM-level flagW.stable() checked after each evaluate call
IntrospectionOpaque_telemetry captures per-evaluate snapshots generically
Autonomous operationRequires reflectorLocal fallback clock via makeMeta.startAutonomous()
Sub-tick schedulingNot supportedfuture(delay < SUBTICK_MS) drains within current tick

All time is logical — no Date.now(), no wall-clock dependency in the model. The reflector stamps each pulse with:

logicalTime = lt (tick counter, increments by 1 per pulse)
wallTime = lt (pure tick count — 1 logical unit per tick)

REFLECTOR_MS = 50 is only the real-time heartbeat rate. World programs never see it.

ctx.future(delay, msg, payload) schedules msg when wallTime >= currentWallTime + delay:

future(1) → next reflector tick
future(0.5) → sub-tick: fires within current tick's drain pass
future(0.001) → sub-tick: fires immediately in drain
future(60) → 60 ticks from now

The drain boundary is SUBTICK_MS = 1. Any future with delay < 1 is a sub-tick.

A pulse is the fundamental unit of shared time, produced by the external Reflector at regular intervals and delivered to all peers. Every pulse carries:

  • logicalTime — monotonically increasing integer, identical for all peers
  • wallTime — equal to logicalTime (pure tick count)

A wave is the complete lifecycle of computation triggered by a single logical pulse. A wave begins when a pulse arrives at a world, propagates through the node graph, and ends when the world reaches stability. Each peer runs its own wave independently.

The wavefront is the propagating boundary of settled computation within a wave. As each node processes its ready entries and emits futures, the frontier of “what has been computed” advances through the graph.

A phase is one stage within a wave:

  • Macro phase — triggered by arrival of a new shared pulse. Every node receives the __macro message once per logicalTime.
  • Micro phase (sub-tick) — one or more local iterations that settle inter-node dependencies and drain queued futures.

Warp handles the case where a new shared pulse arrives with logicalTime > lastLT + 1 — the peer missed one or more pulses. The evaluator synthetically advances wallTime through the remaining queue entries until stable. Three conditions must all hold before warp fires:

  1. pulse.logicalTime > lastLT + 1 (gap detected)
  2. lastLT > 0 (not the very first pulse)
  3. !world.isStable (the previous tick did not fully settle)

Drain exhausts all ready queue entries within a micro phase. Stop condition: _worldNextAt(world) >= wallTime + SUBTICK_MS. When that minimum is beyond the current tick boundary, no node has any ready work.

A world is stable when three conditions all hold:

  1. All node queues contain only entries with fireAt > wallTime (no ready work remaining)
  2. No node is mid-feedback-loop (_depth === 0 on all nodes)
  3. The shared outbox is empty — no ctx.send() message is pending delivery

┌─────────────────────────────────────────────────────┐
│ Reflector │
│ Stamps pulse once. Delivers to all peers. │
└────────────────────┬────────────────────────────────┘
│ pulse { lt, wallTime=lt }
┌────────────────────▼────────────────────────────────┐
│ Meta Program │
│ Orchestrates worlds. Drives the wavefront. │
│ Warp · Drain · Stability check · UI sync │
│ startAutonomous() — local fallback clock │
└────────────────────┬────────────────────────────────┘
│ registerEvent / evaluate
┌────────────────────▼────────────────────────────────┐
│ World (ProgramState) │
│ Hosts W nodes: Behaviors.collect + W.reduce │
│ Each node: W.reduce → local queue → futures │
└────────────────────┬────────────────────────────────┘
│ handler(state, payload, ctx)
┌────────────────────▼────────────────────────────────┐
│ W.reduce(state, pulse, nodeId, handlers) │
│ ctx: future · send · feedback · futureInf │
│ localReflector │
└────────────────────┬────────────────────────────────┘
│ W.export → isStable, logicalTime
┌────────────────────▼────────────────────────────────┐
│ Host layer │
│ _worldNextAt · _worldSnapshot · _uiRefresh │
└─────────────────────────────────────────────────────┘

META_PROGRAM is a Renkon program that runs above the world programs. It receives pulses from the Reflector via a queued receiver and drives the wavefront for each registered world.

For each pulse × world, in order:

  1. WARP — fires only when the peer missed ticks and the prior tick didn’t settle
  2. MACRO — fires on every new pulse. Increments _currentEvalGen, calls registerEvent(pulse) + evaluate()
  3. DRAIN — loops while !world.isStable && _worldNextAt(world) < wallTime + SUBTICK_MS. Safety cap: 10000 iterations
  4. Outbox flush — one extra evaluate, only if world._outbox is non-empty after drain

W is the functional core of each node. Its reduce function takes (state, pulse, nodeId, handlers) and returns a new state. On each call it:

  1. Restore check — if pulse._restoreState[nodeId] exists, return it immediately (snapshot replay)
  2. Inbound outbox — collects messages from appRef._outbox[nodeId] where _evalGen < _currentEvalGen
  3. Queue split — splits state._queue into ready (fireAt ≤ wallTime) and later (fireAt > wallTime)
  4. __macro injection — if !isSubTick && (state._lt ?? -1) !== logicalTime, prepends __macro to allReady
  5. Handler dispatch — iterates allReady, calls handlers[entry.msg](userState, entry.payload, ctx)
  6. Effect collectionctx.future pushes {kind:"future", fireAt: wallTime+delay, ...}
  7. Returns { ...userState, _queue: newQueue, _nextAt, _depth, _lt }

Each Behaviors.collect wraps one W node:

const counter = Behaviors.collect(
{ count: 0, started: false },
reflector,
(state, pulse) => W.reduce(state, pulse, "counter", {
__macro: (s, p, ctx) => { ... }, // fires once per logicalTime
newCycle: (s, p, ctx) => { ... }, // fires when future arrives
})
);

Logical time T ──────────────────────────────────────▶
│ │ │
pulse(lt=1) pulse(lt=2) pulse(lt=3) shared, discrete
├── sub-tick 0 (macro phase)
├── sub-tick 0.5 (Zeno step — depth 0..N for feedback loops)
├── sub-tick 0.75
├── ...
└── stable (all fireAt ≥ wallTime+1, all depths 0, outbox empty)

Macro time is shared and observable. Sub-tick time is local and transient.


These invariants must hold for two peers to stay in sync:

  1. wallTime = lt — pure logical tick count. No Date.now() in the model.
  2. The canonical pulse is frozen and delivered unchanged. The Reflector stamps wallTime once and freezes the pulse object.
  3. Warp uses queue-derived wallTime. During warp, wallTime is advanced to _worldNextAt on each iteration — the actual fireAt values already in the queue.
  4. No closures in queue payloads. All future payloads are plain scalars or plain objects.
  5. Stability is locally determined. Each peer settles its own wavefront independently.
  6. Queued pulse receiver. The META_PROGRAM receiver uses {queued: true} — no pulse is silently dropped under jitter or load.
  7. __macro fires at most once per logicalTime. W.reduce tracks _lt and skips __macro injection if the node already processed this logical tick.

makeIfsClock is a deterministic, multiplayer-synchronised fractal cascade that alters the fundamental relationship between time and chaotic flows, suited for non-uniform, fractal time-reparametrisation of any continuous dynamical system.

Multiplayer determinism. This is solved by reseeding W.rng at the start of every cycle using a bijective MurmurHash3 finalizer on the logical time. Unconditionally consuming exactly two RNG values per beat (selfRatio and childRatio) guarantees that every peer follows the identical randomised execution branch.

Cascade mechanics. The tree branching logic:

  • gen < genCap schedules self-loops at the current depth, scaled by a random contraction ratio selfRatio
  • gen === 0 && depth + 1 < depth spawns a child branch into the next nested layer at temporal offset selfDelay + childDelay

Irrational base and contraction ratios. No finite integer product of the contraction ratios can equal an integer multiple of the base delay. This guarantees that scheduled event times are dense and do not collapse onto a periodic lattice — the time increments are truly fractal-distributed.


The evaluator captures a _telemetry map on each world, keyed by logicalTime. Each key holds an array of snapshots — one per evaluate() call during that wave:

{ source: "macro" | "drain" | "warp", iter: Number, nodes: {
[nodeName]: { ...userFields, queueLen, nextAt }
} }

Snapshots are produced by _worldSnapshot — generic, no node-name coupling. The telemetry window is bounded to the last 5 logical times to prevent unbounded growth.


A feedback loop is a wave that deepens through multiple iterations of inter-node exchange before reaching a fixed point.

ctx.feedback(msg, payload, maxDepth) schedules a message at wallTime + _fbStepMs but increments the wave’s depth counter by 1. If depth >= maxDepth the call is a silent no-op.

Depth propagates across node boundaries:

EffectDepth carried
ctx.feedback(msg, payload)depth + 1 — explicit loop increment
ctx.send(target, msg)depth — same wave, preserved across node boundary
ctx.future(0, msg)depth — zero-delay, same wave phase
ctx.future(N, msg) where N > 00 — new real-time phase, depth resets
__macro injection0 — new wave boundary, always resets

The DISCONNECT / RECONNECT button demonstrates peer autonomy.

On Disconnect. meta.startAutonomous() starts a local setInterval(REFLECTOR_MS) injecting pulses:

{ logicalTime: localLt++, wallTime: localLt, _isLocal: true }

All worlds continue animating using purely logical ticks. The local state is deterministic and correct for purely internal computation.

On Reconnect. Animation resumes seamlessly. If any world diverged, warp fires and replays the authoritative state. The speculative work is cleanly discarded.


DecisionRationale
wallTime = lt not Date.now()Pure determinism — immune to real-time jitter
Single reflector for all worldsOne clock, all peers
Future delays in logical ticksNo unit confusion, scale-independent
SUBTICK_MS = 1 boundaryClean two-level clock without extra machinery
Warp only on LT > lastLT + 1Logical gaps only — normal ticks drain naturally
future(0) chains drain synchronouslyArbitrary within-tick computation
ctx.feedback with depth trackingConvergence loops as observable wavefront property
W.localReflector mixinSelf-hosting clock, autonomous operation
Outbox flush after drainCross-node sends settle before stability check
No separate wave shimAll worlds share one reflector

  • Hardware Description Languages (VHDL / Verilog) — The Macro-tick is the “Clock Signal” of the CPU. The Micro-ticks are called “Delta Cycles.” Inside one clock cycle, electricity flows through gates; the simulator “drains” these flips until the circuit is stable before moving to the next clock pulse.

  • Parallel Discrete Event Simulation (PDES) — In large-scale military or weather simulations, the Chandy-Misra-Bryant algorithm or Time Warp (Jefferson) allow different “Islands” of the simulation to process their own local queues.

  • Distributed Databases (Vector Clocks) — Systems like Amazon’s Dynamo use logical clocks to determine the order of events across different servers.


npm install
npm start

That will start a local Reflector and server for hosting static files.

Open web browser: http://localhost:3000 — list demo apps.

URL params for demo page: ?app=appName&k=seloID

Load kwe-index.renkon in a local/remote running instance of Renkon Pad. The evaluator runs directly in Renkon Pad with no build step.