A lava lamp simulation served two ways: as ANSI half-blocks over SSH, or as
RGBA pixels on a <canvas> in the browser. One Rust simulation, two
output formats, one self-contained static binary.
script/serverThen from any other terminal:
ssh -p 2222 localhost # SSH transport
open http://localhost:8080/ # web transport (WASM in your browser)script/server does three things on each invocation:
script/setupβ generates a dev SSH host key in.dev/(gitignored) if missing.script/build-wasmβwasm-packs the engine towasm32-unknown-unknownsolava-webcan embed it.cargo run --release -p lavaβ runs the all-in-one binary that hosts both transports.
By SSH username or by URL path β same parser, same aliases.
ssh ultraviolet@localhost # or `uv@`, `blacklight@`
ssh pink@localhost # β bubblegum
ssh ice@localhost # β oceanhttp://localhost:8080/aurora
http://localhost:8080/toxic
http://localhost:8080/uv
Anything that doesn't parse falls back to classic. Once connected,
β / β cycles palettes (the new name flashes briefly in the
bottom-left badge), and left-click anywhere on the lamp to heat that
spot β nearby blobs warm up and rise.
For the full palette list + aliases, connect as help:
ssh help@localhostPrints a colored cheat-sheet and disconnects (no PTY required, no connection slot consumed).
All env vars; both transports read what they need from the same environment.
| Var | Type | Default | Used by | Description |
|---|---|---|---|---|
LAVA_PORT |
u16 | 2222 |
ssh | SSH listen port |
LAVA_HOST_KEY |
string | (required) | ssh | Contents of an OpenSSH-format private host key (not a path) |
LAVA_HOST_KEY_PASSWORD |
string | (none) | ssh | Passphrase for LAVA_HOST_KEY if it's encrypted |
LAVA_MAX_CONN_TIME |
u64 | 300 |
ssh | Hard session timeout, in seconds |
LAVA_MAX_PER_IP |
usize | 3 |
ssh | Concurrent SSH connections per IP |
LAVA_SPEED |
f32 | 0.8 |
ssh | Simulation speed multiplier (1.0 = engine "natural" rate, lower = slower) |
LAVA_WEB_PORT |
u16 | 8080 |
web | HTTP listen port |
RUST_LOG |
string | lava=info,β¦ |
both | tracing-subscriber filter |
Logs are pretty-printed when stdout is a TTY and JSON otherwise. SSH events
include peer (IP:port), cols/rows, term (client $TERM), banner
(client SSH version), palette, duration_secs, and a structured reason
(client_exit, timeout, disconnect, write_failed).
ββββββββββββββββββββββββββββββ
β lava-engine β pure Rust, no I/O
β sim Β· palette Β· session β
β ββββββββββββ βββββββββββ β
β β term β β pixels β β two output paths
β β (ANSI) β β (RGBA) β β same simulation
β ββββββββββββ βββββββββββ β
βββββββ¬βββββββββββββββ¬ββββββββ
β β
βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β lava-ssh β β lava-wasm β
β (russh) β β(wasm-bindgen)β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ
β β
β βΌ
β ββββββββββββββββ
β β lava-web β axum static server,
β β β embeds wasm bundle
β ββββββββ¬ββββββββ
βΌ βΌ
ββββββββββββββββββββββββββββββ
β lava β single static binary,
β tokio::try_join!(ssh, web) β runs both servers
ββββββββββββββββββββββββββββββ
The browser runs the simulation client-side via WebAssembly. No
WebSocket, no per-connection state, no rate limits β lava-web is pure
static hosting and the page calls wasm.renderRgba() β ctx.putImageData
on every animation frame. The whole web bundle (HTML, JS, WASM) is
include_bytes!'d into the binary.
ANSI / terminal output:
use lava_engine::{Palette, Session};
let mut session = Session::new(80, 30, Palette::Bubblegum);
let mut frame = Vec::new();
loop {
session.tick(1.0 / 30.0);
frame.clear();
session.render(&mut frame);
// write `frame` bytes to a PTY, stdout, β¦
}RGBA pixel output (same engine, different sink):
session.render_rgba(&mut frame);
// frame is `width * height * 4` bytes β feed to a canvas, PNG encoder, β¦lava/
βββ crates/
β βββ lava/ single-binary entrypoint (ssh + web)
β βββ lava-engine/ simulation, palettes, term + pixels renderers, Session
β βββ lava-ssh/ SSH server library (russh)
β βββ lava-wasm/ wasm-bindgen wrapper exposing the canvas API
β βββ lava-web/ axum static-asset server library
βββ script/
βββ setup generate dev host key (idempotent)
βββ build-wasm wasm-pack build β static bundle
βββ server setup + build-wasm + run unified binary
βββ test cargo fmt --check + clippy + test
MIT