From bounded sessions to continuous operation: pluggable chain modules in Myelin

Epoch production, genesis-bound finality, durable recovery, and a Veloren integration experiment.

NOTE
In this post, pluggable means compiled-in and selected at session creation, never hot-swapped. Myelin-finalised means accepted by the genesis-bound closed-validator module and durably stored; it does not mean finalised on CKB.
The previous post, ‘Introducing Myelin: a CKB-aligned off-chain Cell session runtime’describes the core proposition: run finite Cell transitions off-chain, preserve CKB transaction and VM concepts, and retain an explicit evidence path for projection and bounded disputes.

This post begins with the next problem.

Myelin is initially designed to be a xuejie-style finite-Cell session runtime. Real applications may stay alive for hours, accept input many times a second and even serve the same community for years. How can something bounded support something that feels continuous?


Each step stays finite. The service continues by carrying one verified result into the next step.

A long-lived world still advances in finite steps

xuejie’s Teeworlds experiment separated the deterministic game loop from graphics and networking, then replayed recorded player inputs inside CKB-VM. One Hour One Life carried the same method into a world with no natural ending: once a minute, a finite tape advanced one committed world-state hash to the next. Archipelagos added a spatial boundary, with each region held in its own game Cell and ports connecting the wider world.

These experiments establish the computational model. Myelin addresses the operational problem around it: when to close an epoch, how to preserve work while finality is pending, and how to resume from the durable head after a crash.

The pressure came from live gaming

I recently joined Retric’s discussion on porting a Counter-Strike-style game to Fiber. His OpenStrike Fiber Arena is a particularly useful engineering model because its boundaries are easy to see.

The authoritative server runs the game simulation at 64 ticks per second. Renet carries latency-sensitive inputs and snapshots. Fiber sits beside that hot path: before the match, the players authorise hold invoices; when the server records enough damage, it releases the corresponding preimage and the payment becomes claimable. With the default 25-damage bucket, four invoices cover one player’s 100 HP. The matchmaker and game server never need the players’ wallet keys or direct control of their Fiber nodes.

That is a strong baseline. The game remains responsive, payment is pre-authorised and the loser’s refusal cannot undo damage that the server has already settled. It also gives the server considerable authority: the server decides which hit occurred and possesses the information that releases value. A bounded spending cap limits the blast radius, while production hardening still needs short-lived match keys, separation between simulation and settlement signing, an append-only event log and commitments that bind releases to a sequence and match state.

The more revealing pressure appears when a neat 1v1 demo becomes a service expected to run every day or later scale up to multi-player sessions.

A match contains more events than it first appears

For the same discussion, I looked at the aggregate figures published by CS2 Tracker: roughly 37 million matches, 748.6 million rounds and 5.4 billion kills at the time of the survey. The arithmetic is simple, and useful precisely because it gives an order of magnitude.

From the published totals Approximate result
748.6 million rounds ÷ 37 million matches 20.2 rounds per match
5.4 billion kills ÷ 37 million matches 146 kills per match
146 kills × four 25-HP buckets 584 threshold events per match

The last line is a rough estimate. Players do not always lose exactly 100 fresh HP before every kill, and a real damage stream contains its own edge cases. I use 584 as a capacity probe. It tells us that a production FPS match can create hundreds of economically interesting events without doing anything exotic. Finer buckets, healing or a different 5v5 ruleset can push the planning case beyond a thousand.

At that scale, one pre-created hold invoice for every event starts to charge rent in several places at once. Conditional-transfer slots remain occupied, liquidity stays reserved, the pre-match handshake grows, and cancellation, timeout and crash recovery become more complicated. An attacker also gains a cheap way to reserve scarce resources and abandon them. In a multiplayer game, pairwise channel and liquidity relationships add another dimension; a hub can simplify topology, though shared liability across several possible payees still needs an explicit model.

This event volume suggests settling bounded epochs rather than individual damage buckets. For example, if A deals 75 damage to B while B deals 50 to A, five gross obligations can be reduced to one net obligation from B to A.

The checkpoint needs enough underlying data to be rebuilt. A useful record carries its sequence, previous checkpoint hash, gross debits and credits, net balances, consumed reservations, transcript commitment and expiry. A client, watchtower or replay service can then check conservation, detect a duplicate release and recover after a server restart. The server remains authoritative in the first trust profile; its history becomes bounded, reconstructible and auditable.

This is the bridge from a finite session to continuous operation. The game loop never waits for finality. Completed application epochs may be journalled asynchronously, but they do not become speculative finalised descendants. The single writer prepares only the next reserved epoch against the current durable head; later epochs are queued until that head advances. A configured limit bounds this backlog. If checkpointing falls behind, the system has an explicit choice—pause billable damage, continue without economic effects or end the match—before liability escapes its bound.

Long-running operation is therefore a chain of small, closed decisions. Production decides when an epoch is ready. Execution checks it. Finality accepts one exact result. Storage advances the durable head, and recovery proves that the next epoch really follows it. Those are the chain modules this post is about.

How the session chain advances

A Myelin chain is an append-only sequence of MyelinBlocks built from one genesis state. The session head records the latest finalised block hash, height and state root. To extend the chain, a candidate block must name that head as its parent and use the head’s state root as state_root_before. It then commits to the ordered raw transaction IDs, the resulting state_root_after, the scheduler report and the relevant data commitments.

The block is appended only after two checks succeed. The execution path must verify the Cell transaction scripts in CKB-VM, apply the transitions against the exact pre-state root and produce the committed post-state; the finality verifier must then validate a proof for the exact canonical block under the module fixed in session genesis. Myelin writes the finalised block, new state checkpoint, new head and outbox entries atomically. If the parent, pre-state root or proof is stale or inconsistent, the head does not move. The next block can therefore begin only from the last result that was both verified and durably committed.

This chain is local to one Myelin session. It is not part of CKB’s blockchain and nor does it inherit CKB’s Nakamoto finality. CKB-VM determines whether each Cell transition is valid; the configured closed-validator module determines whether the session operators accept the resulting MyelinBlock. The chain preserves their exact order and gives recovery one durable point from which to continue.

Once that sequence existed, a second problem became visible. The first prototype could choose different finality engines, yet the choice had spread through the repository. The session knew concrete proof types. Storage knew their shapes. Networking knew Tendermint message names. Adding one engine could become a tour of the entire codebase.

Concrete consensus types had leaked into session, storage and networking code.

Modularity: isolating knowledge and state boundaries

The newer design limits each layer to a narrow interface. The session asks a verifier to check one exact block and typed proof. A consensus module owns its proof and voting messages. The network authenticates and carries an opaque module message. RocksDB stores the resulting record and guards its identity. A small runtime host selects the compiled components and starts them in dependency order.

This is the practical meaning of a “pluggable chain module” in Myelin. A different compiled-in implementation may be selected for a new session behind a narrow boundary, and its output is still checked locally. The Cell transition, its raw transaction identity and the CKB-VM result keep the same meaning as the machinery around them changes.

That design raised another question. Consensus determines how the session operators accept a result. An earlier decision sits outside consensus: when has the current batch gathered enough work to become a candidate block?

A useful detour through FuelVM

That question took me first to FuelVM, then one step further into Fuel Core.

FuelVM is the register-based virtual machine at the core of the Fuel stack. It defines how Fuel transactions execute in a UTXO-based system: predicates express spending conditions, a transaction script provides the entry point, and contracts provide persistent state. Fuel Core is the node implementation around that execution engine. Among its other services, it exposes a useful boundary between transaction execution and block production: FuelVM defines execution semantics, while the node decides when a batch is ready to execute.

At the source revision I studied, Fuel Core’s PoA producer defines four trigger policies. The easiest way to read them is to picture a shallow tray beside the producer. Transactions arrive and wait in the tray. The trigger has one small job: decide when that tray is ready to be handed on.

Policy In plain English What it feels like
Instant ‘A transaction has arrived. Hand the batch on now.’ quick response, usually with smaller batches
Interval ‘At each tick, hand on whatever is waiting.’ a regular rhythm; empty batches are an explicit operator choice
Open ‘When work first arrives, keep the tray open for a short while.’ idle sessions stay idle and later arrivals can join the same batch
Never ‘Wait until somebody asks for a batch.’ exact control for operators, tests and replays

Fuel Core’s separation of production, execution and import thus nicely informed the corresponding module boundary in Myelin.

The trigger families correspond by role.

Bringing those four rhythms into Myelin

That study led directly to a new module, myelin-session-producer. Myelin now carries the same four named trigger families, adapted to its own session model. The names describe the rhythm; every policy still hands over a finite, ordered batch with the same count and byte checks.

The concept of reservation is critical here. If a producer removes work from the queue before finality is reached, a temporary signing failure could cause valid transactions to vanish. The new transaction-source interface keeps each selection in reserve while its candidate is in flight. A successful atomic head advance acknowledges those reservations. A failed commit or an orderly shutdown releases them for a later attempt.

Production is serialised through one writer, so an automatic tick and a manual request cannot race the same session head. The producer awaits the commit result before considering the next batch: one session has at most one candidate block in flight. It checks transaction count and encoded bytes before hand-off, and myelin-session checks them again while preparing the block. During an Open window, manual production stays unavailable; one live window means one comprehensible candidate.

The final hand-off is deliberately demanding. A CandidateCommitter receives a fixed vector and proposed timestamp. Its implementation must execute through the session, obtain the genesis-bound finality proof, verify that proof against the exact block, and advance the block, head, snapshot and outbox atomically. Only then does the producer receive a durable block height and hash.

The trigger controls the tempo. CKB-VM, finality and durable storage keep their own jobs. A controlled game session may choose Instant for responsiveness, a simulation may use Open for denser batches, and a replay harness may choose Never for exact control. Their transition rules are unchanged.

The finality module is part of session genesis

Production timing is an operational choice. Finality reaches deeper into the identity of a session, so Myelin treats its selection differently.

The current closed catalogue contains three compiled choices. A static committee accepts a configured weighted quorum over the same block. Proof of authority assigns each height to a known authority and depends on that signer being available. Tendermint moves a known validator set through proposal, prevote and precommit rounds until more than two thirds of the configured voting power reaches a decision. A common verifier interface does not make their safety and liveness assumptions equal.

These are three trust arrangements for controlled sessions, sharing one application-execution path. Their common contract requires the same transaction batch to produce the same raw transaction identities, scheduler commitment, execution order and before-and-after state roots. The production gate exercises that invariance across the built-in engines. A mismatch there is an execution-layer protocol failure. Only consensus-bound block and proof material may vary.

When a session is created, Myelin selects one module from the catalogue and commits the consensus kind, canonical validator or authority configuration, compiled module descriptor and WAL schema in genesis. Proofs, network envelopes, recovery logs and finalised records all lead back to that choice.

Selection happens between sessions. Within one session, recovery expects the same module and configuration before the writer can reopen.

If a service restarts with another authority set or proof format, recovery refuses to reinterpret the old chain and keeps the writer closed. This is static registration with runtime selection. A Rust trait describes the socket; the genesis commitments identify what actually occupies it. Continuous operation does not require one immortal session. A validator-set, signing-key, module, proof-format or WAL-schema change closes the current session and starts a successor. That successor should bind its genesis to the predecessor’s finalised head or settlement receipt, allowing the application to evolve without asking recovery to reinterpret old history. This is an upgrade boundary between sessions, not an in-session hot-swap path.

A session is immutable, though it need not be immortal. A service that runs for years will eventually need key rotation, a new operator set or a module upgrade. The safe direction is an explicit successor session: the old session finalises a handover checkpoint, and the new genesis binds that predecessor head together with its new module and configuration. (That handover protocol remains future work; the current runtime keeps identity fixed for the life of a session.)

The driver moves; the verifier checks

The finality driver performs the active work. It chooses the scheduled PoA signer, gathers committee signatures or advances Tendermint rounds. The verifier has a calmer task: check the returned proof against the exact block, the module commitment and the validator configuration fixed for this session.

An application coordinator may say ‘success: true’; the session advances only after local proof verification. A dead coordinator can halt progress, which is a liveness failure. It gains no path around the verifier, which protects safety.

Continuous operation also depends on quieter modules. The network carries authenticated envelopes bound to the session, module commitment, sender, recipient, sequence and payload hash. It acknowledges a message after durable storage, accepts an exact retry idempotently, and treats different content at an accepted sequence as equivocation.

RocksDB commits the finalised block, durable head, current state checkpoint and outbox entries in one atomic operation. On restart, Myelin restores the latest checkpoint, audits the ordered block-and-proof lineage—parent hash, height, state roots, timestamp, module commitment and exact finality proof—then checks that the restored executor root equals the durable head. The writer opens only after those checks pass. Normal recovery does not re-execute every historical transaction; full historical replay remains a separate future audit mode.

Atomicity ends at the session store. Outbox delivery to an external game server or settlement adapter is at least once, and handlers must be idempotent on the deterministic message ID. Per-transition limits also leave one longer-horizon question: history and audit time still grow with the session. RocksDB already keeps a current checkpoint and only periodic archival snapshots, while pruning and checkpointed deep audit remain lifecycle work for truly long-lived deployments.

The runtime host brings storage and recovery up before the writer and consensus driver, closes the writer when a critical service fails, and shuts services down in reverse dependency order.

An RPG-shaped experiment

I have also adapted a fork of Veloren to Myelin.

Veloren is an open-source multiplayer voxel RPG written in Rust, set in a procedurally generated fantasy world with combat, NPCs, crafting and multiplayer servers. I chose it because it is a real, stateful game rather than a narrow payment demo: its long-running world, inventory and economy expose the ordering, recovery and asset-boundary problems that a continuous Myelin session has to handle.

An RPG world’s important events arrive unevenly and its history must survive restarts, making it a useful test for a long-running chain of finite transitions. The current fork follows a selective policy: only authoritative events with lasting game meaning, together with important asset changes, enter its Myelin journal. Movement, render frames, physics ticks and other transient activity stay in Veloren’s ordinary hot path. This selectivity belongs to the Veloren adapter and this experiment; it is one operating choice among many that Myelin is built to accommodate.

The producer policy decides when to close an epoch. In the current lazy Open profile, the first event starts a 100 ms window, 1,024 events close it early, and an idle world produces no blocks. At closure, Veloren fixes one sequence range and hands it to a background worker; the bridge turns it into bounded CellTx blocks. Myelin executes their transitions through CKB-VM, verifies the configured finality proof and commits the result atomically in RocksDB. Only then does Veloren advance the finalised sequence; the journal preserves any pending work across a crash.

Veloren chooses the events. Myelin closes finite batches and makes their history durable.

Veloren owns event meaning and wallet UX. In this experiment it chooses selected authoritative events, a lazy Open window, no empty blocks and an application-level event cap. In the meanwhile, Myelin keeps application-neutral: its producer consumes an adapter-supplied transaction source, supports Instant, Interval, Open and Never with manual production, offers explicit empty-interval production, and enforces configurable transaction-count and byte limits. Another host can choose a different event vocabulary and production rhythm while retaining the same finite CellTx, execution, proof-verification, atomic-storage and recovery contracts.

The Myelin-enabled Veloren client with standard CKB and JoyID wallet entry points.

On this fork, I may next explore Spore/DOB-backed persistent game objects and their application-level settlement flows, all implemented at the application layer on top of the same session and settlement boundaries.

If you are building games, exploring L2s, or tackling similar state-reconciliation challenges, I’d love to connect. Feel free to reply here or reach out directly.

9 Likes