Architecture
Architecture overview and core concepts for the Krestianstvo Wavefront Evaluator.
Live demo: wavefront.krestianstvo.org
Source code: github.com/NikolaySuslov/krestianstvo-wavefront-evaluator
Introduction
Section titled “Introduction”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 Relation to Physics
Section titled “The Relation to Physics”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.
| Concept | Physics Equivalent | Wavefront Implementation |
|---|---|---|
| Pulse | Universal Time | The Reflector Heartbeat |
| Node Queue | Local Particle State | W.reduce local _Q |
| Micro-tick | Particle Interaction | The “Drain” / Feedback loop |
| Stability | Thermal Equilibrium | When 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”| Dimension | Krestianstvo VM | Krestianstvo Wavefront Evaluator |
|---|---|---|
| Message queue | Centralised — one shared queue per world | Decentralised — each node owns its own local queue |
| Time authority | VM clock drives all nodes uniformly | Two-layer time: shared logical pulse + local micro-tick |
| Causality | Enforced by queue ordering at VM level | Emerges from wavefront propagation across node dependencies |
| Sub-step execution | Async, RAF-driven | Synchronous drain loop — sub-ticks are fractions of a logical tick |
| Late-join / desync recovery | Manual snapshot and replay | Warp mechanism — local clock-advance to catch up |
| Node communication | VM routes messages between nodes centrally | ctx.send() writes to a per-world outbox; nodes pull on next evaluate |
| Stability detection | VM-level flag | W.stable() checked after each evaluate call |
| Introspection | Opaque | _telemetry captures per-evaluate snapshots generically |
| Autonomous operation | Requires reflector | Local fallback clock via makeMeta.startAutonomous() |
| Sub-tick scheduling | Not supported | future(delay < SUBTICK_MS) drains within current tick |
Core Vocabulary
Section titled “Core Vocabulary”Pure Logical Time
Section titled “Pure Logical Time”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.
Future Scheduling
Section titled “Future Scheduling”ctx.future(delay, msg, payload) schedules msg when wallTime >= currentWallTime + delay:
future(1) → next reflector tickfuture(0.5) → sub-tick: fires within current tick's drain passfuture(0.001) → sub-tick: fires immediately in drainfuture(60) → 60 ticks from nowThe 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 peerswallTime— equal tologicalTime(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.
Wavefront
Section titled “Wavefront”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
__macromessage once perlogicalTime. - 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:
pulse.logicalTime > lastLT + 1(gap detected)lastLT > 0(not the very first pulse)!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.
Stability
Section titled “Stability”A world is stable when three conditions all hold:
- All node queues contain only entries with
fireAt > wallTime(no ready work remaining) - No node is mid-feedback-loop (
_depth === 0on all nodes) - The shared outbox is empty — no
ctx.send()message is pending delivery
Architecture Layers
Section titled “Architecture Layers”┌─────────────────────────────────────────────────────┐│ 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
Section titled “Meta Program”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:
- WARP — fires only when the peer missed ticks and the prior tick didn’t settle
- MACRO — fires on every new pulse. Increments
_currentEvalGen, callsregisterEvent(pulse)+evaluate() - DRAIN — loops while
!world.isStable && _worldNextAt(world) < wallTime + SUBTICK_MS. Safety cap: 10000 iterations - Outbox flush — one extra evaluate, only if
world._outboxis non-empty after drain
W — the Node Runtime
Section titled “W — the Node Runtime”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:
- Restore check — if
pulse._restoreState[nodeId]exists, return it immediately (snapshot replay) - Inbound outbox — collects messages from
appRef._outbox[nodeId]where_evalGen < _currentEvalGen - Queue split — splits
state._queueintoready(fireAt ≤ wallTime) andlater(fireAt > wallTime) __macroinjection — if!isSubTick && (state._lt ?? -1) !== logicalTime, prepends__macrotoallReady- Handler dispatch — iterates
allReady, callshandlers[entry.msg](userState, entry.payload, ctx) - Effect collection —
ctx.futurepushes{kind:"future", fireAt: wallTime+delay, ...} - 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 }));Two-Layer Time
Section titled “Two-Layer Time”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.
Distributed Determinism Invariants
Section titled “Distributed Determinism Invariants”These invariants must hold for two peers to stay in sync:
wallTime = lt— pure logical tick count. NoDate.now()in the model.- The canonical pulse is frozen and delivered unchanged. The Reflector stamps
wallTimeonce and freezes the pulse object. - Warp uses queue-derived
wallTime. During warp,wallTimeis advanced to_worldNextAton each iteration — the actualfireAtvalues already in the queue. - No closures in queue payloads. All future payloads are plain scalars or plain objects.
- Stability is locally determined. Each peer settles its own wavefront independently.
- Queued pulse receiver. The META_PROGRAM receiver uses
{queued: true}— no pulse is silently dropped under jitter or load. __macrofires at most once perlogicalTime.W.reducetracks_ltand skips__macroinjection if the node already processed this logical tick.
Fractal IFS-Based Clock
Section titled “Fractal IFS-Based Clock”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 < genCapschedules self-loops at the current depth, scaled by a random contraction ratioselfRatiogen === 0 && depth + 1 < depthspawns a child branch into the next nested layer at temporal offsetselfDelay + 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.
Telemetry
Section titled “Telemetry”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.
Feedback Loop
Section titled “Feedback Loop”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:
| Effect | Depth 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 > 0 | 0 — new real-time phase, depth resets |
__macro injection | 0 — new wave boundary, always resets |
Autonomous Mode
Section titled “Autonomous Mode”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.
Key Architectural Decisions
Section titled “Key Architectural Decisions”| Decision | Rationale |
|---|---|
wallTime = lt not Date.now() | Pure determinism — immune to real-time jitter |
| Single reflector for all worlds | One clock, all peers |
| Future delays in logical ticks | No unit confusion, scale-independent |
SUBTICK_MS = 1 boundary | Clean two-level clock without extra machinery |
Warp only on LT > lastLT + 1 | Logical gaps only — normal ticks drain naturally |
future(0) chains drain synchronously | Arbitrary within-tick computation |
ctx.feedback with depth tracking | Convergence loops as observable wavefront property |
W.localReflector mixin | Self-hosting clock, autonomous operation |
| Outbox flush after drain | Cross-node sends settle before stability check |
| No separate wave shim | All worlds share one reflector |
Related Works
Section titled “Related Works”-
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.
Getting Started
Section titled “Getting Started”npm installnpm startThat 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
Running in Renkon Pad
Section titled “Running in Renkon Pad”Load kwe-index.renkon in a local/remote running instance of Renkon Pad. The evaluator runs directly in Renkon Pad with no build step.