System Architecture
CrawlCipher is designed as a hybrid dApp split into three decoupled components: an interactive Rust TUI wrapper, a deterministic C# NativeAOT engine, and a Soroban Smart Contract on the Stellar blockchain.
Architecture Diagram
The overall structure and interface bridges are diagrammed below:
+-----------------------------------------------------------------------+
| RUST TUI (crawlcipher) |
| +-----------------------+ +------------------+ +----------+ |
| | Terminal GUI | | Dynamic Loader | | Stellar | |
| | (Ratatui/Crossterm) |===> | (libloading) | | API & CLI| |
| +-----------+-----------+ +--------+---------+ +----+-----+ |
+--------------|--------------------------|-------------------|---------+
| | |
| (Reads Block Hash) | (Invokes C-ABI) | (Invokes contract)
v v v
+--------------|--------------------------|-------------------|---------+
| | | | |
| +-----------v-----------+ +--------v---------+ +---v------+ |
| | Deterministic Seed | | C-ABI FFI Layer | | Soroban | |
| | Entropy Loader | | (FFIExports) | | Contract | |
| +-----------+-----------+ +--------+---------+ +----------+ |
| | | |
| v v |
| +-----------------------+ +--------+---------+ |
| | Deterministic RNG |===> | Game Core | |
| | (System.Random) | | (Grid & Snake) | |
| +-----------------------+ +--------+---------+ |
| | |
| v |
| +------------------+ |
| | Input Log | |
| | (Replay Record) | |
| +------------------+ |
| C# CORE ENGINE (NativeAOT Shared Library) |
+-----------------------------------------------------------------------+
1. Subsystems
A. Terminal Interface (Rust / Ratatui)
The frontend terminal layout is built in Rust using the ratatui crate. It coordinates:
- UI & Input: Capture inputs (keyboard/mouse) via
crosstermraw mode, map mouse click coordinates to grid coordinates, and render status bars, game boards, menus, and overlays. - Dynamic Link Loading: Instead of compile-time linking, the frontend dynamically loads
libCrawlCipher.Core.so(Linux) orCrawlCipher.Core.dll(Windows) using thelibloadingcrate. This allows swapping core logic without recompiling the UI binary. - Blockchain Connectivity: Interacts with the Stellar network by calling Horizon REST APIs directly (for account stats and block hashes) and spawning the
stellarCLI tool to interact with Soroban contracts.
Planned — not yet in the client
Stellar has deprecated Horizon in favor of Stellar RPC; migrating the client’s REST calls is tracked internally.
B. Simulation Core (C# / .NET 8.0 NativeAOT)
The underlying simulation state and logic are written in C# (CrawlCipher.Core).
- Native AOT: Compiled using Microsoft’s .NET NativeAOT toolchain. This publishes the managed C# library as a standard, self-contained unmanaged shared library (DLL/SO) with zero .NET runtime dependency requirements on the host machine.
- Deterministic Loop: Tracks coordinates, snakes, entities, grids, and tick counts. To remain 100% replayable and fair, it forbids calling
DateTime.Nowor system timers, relying solely on a seed-linkedSystem.Randomgenerator. - C-ABI Export Layer: Exposes unmanaged entry points via
[UnmanagedCallersOnly]so the Rust frontend can map struct layouts sequentially in memory and invoke actions.
C. Soroban Session Lock Contract (Rust / WASM)
A Soroban smart contract deployed on the Stellar testnet (smart-contracts/session-lock):
- Asset Lock (
lock_session): Locks a list of game items (identified by asset IDs) for a user address to prevent trading or double-spending during an active game session. - Release and Prove (
unlock_session): Releases the lock when the client submits the final cryptographic session verification hash.
2. The FFI Bridge (C-ABI)
The Rust TUI interacts with the C# core through a thin foreign function interface (FFI) layer. Data layouts must match exactly across the compiler boundary:
Struct Alignments
- C# representation (
[StructLayout(LayoutKind.Sequential)]) ensures variables are arranged sequentially in memory. - Rust representation (
#[repr(C)]) maps these byte arrays exactly.
Example FFI Export (C#)
[UnmanagedCallersOnly(EntryPoint = "ProcessInput")]
public static unsafe void ProcessInput(IntPtr gamePtr, int inputType, int param1, int param2)
{
var handle = GCHandle.FromIntPtr(gamePtr);
var engine = (GameEngine)handle.Target!;
engine.ProcessInput(inputType, param1, param2);
}Example FFI Loader (Rust)
type FnProcessInput = unsafe extern "C" fn(*mut c_void, i32, i32, i32);
// ...
let process_input_fn: Symbol<FnProcessInput> = lib.get(b"ProcessInput").expect("Missing ProcessInput");