{"id":49311511,"url":"https://github.com/arterialist/live-c-elegans","last_synced_at":"2026-04-26T13:03:24.906Z","repository":{"id":350925586,"uuid":"1208772407","full_name":"arterialist/live-c-elegans","owner":"arterialist","description":"Real-time C. elegans active-inference simulation streamed over WebSocket to a browser canvas — add/remove food live","archived":false,"fork":false,"pushed_at":"2026-04-20T17:36:45.000Z","size":23333,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T19:34:08.032Z","etag":null,"topics":["active-inference","c-elegans","computational-neuroscience","connectome","mujoco","neuroscience","python","real-time","simulation","websocket"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arterialist.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-12T18:14:48.000Z","updated_at":"2026-04-20T17:36:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/arterialist/live-c-elegans","commit_stats":null,"previous_names":["arterialist/live-c-elegans"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/arterialist/live-c-elegans","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arterialist%2Flive-c-elegans","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arterialist%2Flive-c-elegans/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arterialist%2Flive-c-elegans/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arterialist%2Flive-c-elegans/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arterialist","download_url":"https://codeload.github.com/arterialist/live-c-elegans/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arterialist%2Flive-c-elegans/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32297914,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T09:34:17.070Z","status":"ssl_error","status_checked_at":"2026-04-26T09:34:00.993Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["active-inference","c-elegans","computational-neuroscience","connectome","mujoco","neuroscience","python","real-time","simulation","websocket"],"created_at":"2026-04-26T13:03:17.070Z","updated_at":"2026-04-26T13:03:24.901Z","avatar_url":"https://github.com/arterialist.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# C. elegans live WebSocket demo \u0026 virtual lab\n\nThis package ships **two** ways to drive the same [active-inference](https://github.com/arterialist/active-inference) C. elegans stack (PAULA + MuJoCo):\n\n1. **Canvas demo** — `celegans-demo-server` streams compact state to the static **`web/`** client (food add/remove matches the matplotlib interactive viewer: left / right click).\n2. **Virtual lab** — `celegans-lab-server` (**`lab/`**) exposes REST + a richer WebSocket protocol; **`lab-web/`** is a React + Vite app for connectome / body / simulation controls, neuron inspector, and staging patches before apply.\n\n**Public viewer (canvas build):** [https://jimmy.arteriali.st](https://jimmy.arteriali.st) (static `web/`). The browser uses the `ws` query parameter if set, otherwise `DEFAULT_WS_URL` in `web/app.js`—point that constant or `?ws=` at a reachable **`wss://`** server running `celegans-demo-server`.\n\n**Virtual lab — connectome:** full 302-neuron body-aligned map with live firing halos ([`lab-web/README.md`](lab-web/README.md)).\n\n![Connectome — full map](https://raw.githubusercontent.com/arterialist/live-c-elegans/main/images/connectome_full.png)\n\n## Repository layout (local dev)\n\nThis package depends on **`active-inference`** as an editable path dependency (`../active-inference` in `pyproject.toml`). That simulation, in turn, loads PAULA from a **sibling directory** named **`neuron-model`** (see `active-inference/simulations/paula_loader.py`, which prepends `…/neuron-model` to `sys.path`). For `uv sync` / imports to work the same way as a typical local checkout, clone repos **side by side** under a common parent, for example:\n\n```text\nyour-workspace/\n  celegans-live-demo/    # this repo (includes lab/ + lab-web/)\n  active-inference/      # required — editable dep\n  neuron-model/          # required — PAULA ([arterialist/neuron-model](https://github.com/arterialist/neuron-model))\n```\n\nInside **`celegans-live-demo/`**:\n\n- **`lab/`** — FastAPI lab backend (importable package + `celegans-lab-server` entrypoint).\n- **`lab-web/`** — Vite + React + TypeScript frontend for the lab (see [`lab-web/README.md`](lab-web/README.md)).\n- **`web/`** — static canvas client for the classic demo server.\n- **`celegans_live_demo/`** — asyncio WebSocket server for the canvas demo.\n\nIf your PAULA checkout lives under another folder name, symlink or rename it to **`neuron-model`** next to **`active-inference`** so the loader path matches.\n\n## Prerequisites\n\n- Same environment expectations as `active-inference` (Python 3.11+, MuJoCo, connectome cache after first run).\n- **`neuron-model`** and **`active-inference`** available as above; running only from a lone `celegans-live-demo` clone without those siblings will not satisfy the path dependency or PAULA import path.\n- **Virtual lab:** Node.js 20+ (or current LTS) for `lab-web/` (`npm install` / `npm run dev`).\n\n## Virtual lab (`celegans-lab-server` + `lab-web/`)\n\nThe lab runs a **background simulation thread** (same `LabSimRuntime` / connectome as the canvas demo, without food commands) and serves:\n\n- **REST** under `/api/` — health, connectome, per-neuron detail + patch, body/MuJoCo introspection, parameter schema + live/rebuild patches, simulation transport (play / pause / step), pacing, etc. (see `lab/server.py` docstring and `lab/rest_routes.py`).\n- **WebSocket** `GET /ws/state` — lab wire protocol (v5 compact frames: segment geometry, COM, neural summaries, joints, muscles, neuromods; see `lab/wire.py` and `lab-web/src/api/wire.ts`).\n\n**Run locally** (default API + WS on **8765**; Vite proxies to it in dev):\n\n```bash\n# from celegans-live-demo/\nuv sync\nuv run celegans-lab-server\n```\n\n```bash\n# second terminal — from celegans-live-demo/lab-web/\nnpm install\nnpm run dev\n```\n\nThen open the URL Vite prints (typically `http://127.0.0.1:5173`). Production build: `npm run build` in `lab-web/`, then serve `lab-web/dist/` with a reverse proxy to the same backend.\n\nMore detail (architecture, shortcuts, in-app guides): **[`lab-web/README.md`](lab-web/README.md)**.\n\n## Canvas demo server (`celegans-demo-server`)\n\nFrom this directory:\n\n```bash\nuv sync\nuv run celegans-demo-server --host 127.0.0.1 --port 8765\n```\n\nBind `0.0.0.0` if you need LAN access. First startup loads the connectome and settles the MuJoCo body (can take a minute). Server lifecycle, WebSocket clients, food commands (applied in the sim thread), protocol errors, and broadcast failures are logged to stderr with **loguru**; use `--log-level DEBUG` for pings and per-client broadcast timeouts. **Note:** PAULA’s `neuron.setup_neuron_logger` replaces loguru sinks while neurons are built; the demo server reapplies its stderr sink after the simulation is constructed. The new sink uses a **filter** so `neuron.*` logs stay at **WARNING+** (same effect as `build_c_elegans_simulation(..., log_level=\"WARNING\")`: per-tick neuron traffic is mostly `DEBUG` and is suppressed). `--log-level DEBUG` applies to server and `simulations.*` code, not PAULA tick spam.\n\n### Evolved neuromod / params JSON\n\nUse the same JSON shape as `scripts/run_c_elegans.py`: a checkpoint object with `config` or `best_config`, or a flat dict of overrides.\n\n```bash\nuv run celegans-demo-server --evol-config /path/to/evolved_checkpoint.json\n```\n\n### Worm checkpoint (survive restarts)\n\nIf `--snapshot-path` points at a **valid** checkpoint JSON from a previous run (same `checkpoint_version`), startup **imports** MuJoCo pose/velocity, food, PAULA runtime slice, and tick, then continues stepping. If the path is missing or invalid JSON, the sim **resets** as usual. While running, the server **creates or overwrites** that path atomically every `--snapshot-interval-sec` (default **60**).\n\n```bash\nuv run celegans-demo-server --snapshot-path ./worm_state.json --snapshot-interval-sec 60\n```\n\nOn a **first** run the file may not exist yet: the worm starts fresh, and the first disk write happens after one interval (or sooner if you lower the interval for testing).\n\nCheckpoint JSON includes `checkpoint_version` (currently **1**), `tick`, `qpos` / `qvel`, `food_m` (metres), `nervous` (from `export_live_checkpoint` — not full synaptic plasticity), and `saved_at_unix`. In-flight wheel events are cleared on restore; propagation queues are restored and re-heapified.\n\n## Run the static client locally\n\n```bash\ncd web \u0026\u0026 python -m http.server 8080\n```\n\nOpen `http://127.0.0.1:8080/` and pass your local server, for example:\n\n```\nhttp://127.0.0.1:8080/?ws=ws://127.0.0.1:8765\n```\n\n(Otherwise the page uses the checked-in `DEFAULT_WS_URL` in `app.js`, which is set for a public **WSS** tunnel in production builds.)\n\n## How to deploy\n\n**Canvas demo**\n\n1. Deploy the `web/` folder to any static host (GitHub Pages, Netlify, Vercel, etc.). The project’s public static build is served at **[https://jimmy.arteriali.st](https://jimmy.arteriali.st)**.\n2. Set `DEFAULT_WS_URL` in `app.js` to your public **`wss://`** endpoint (Cloudflare Tunnel, ngrok, …) pointing at this server’s port, or rely on `?ws=` for ad-hoc backends.\n3. Keep `celegans-demo-server` + tunnel running while the “live worm” should be visible.\n\n**Virtual lab**\n\n1. Run `celegans-lab-server` (or containerise it) on a reachable host/port with CORS allowed for your frontend origin if different.\n2. Build `lab-web` (`npm run build`) and deploy `lab-web/dist/` behind the same host (path-based) or another static origin; configure the production base URL / proxy so browser calls to `/api` and `/ws` reach the lab server (Vite’s dev proxy is dev-only).\n\n## Protocol (version 3, compact wire)\n\nThis section documents **`celegans-demo-server`** and the static **`web/`** client. The **virtual lab** (`celegans-lab-server` + `lab-web/`) uses a **different** on-the-wire layout: **protocol `p` = 5** on `/ws/state` (segment triplets, COM xyz, expanded neural and body channels — see `lab/wire.py` and `lab-web/src/api/wire.ts`).\n\nWire format uses short keys. **`p` must be `3`** on every frame; otherwise the server replies with `t: \"e\"` (`unsupported protocol version`). There is **no server-side trajectory**: the client appends each centre-of-mass sample to a local trail (max 2000 points).\n\n### Message types (`t`)\n\n| `t` | Direction | Role |\n|-----|-----------|------|\n| `h` | server→client | Hello after connect |\n| `s` | server→client | Simulation snapshot (geometry + neural summary) |\n| `e` | server→client | Error (`m` text) |\n| `o` | server→client | Pong (reply to client `i`) |\n| `u` | server→client | Presence: `n` = concurrent WebSocket client count |\n| `fa` | server→client | Food **added** by viewers — `n` = pellet count since last broadcast window (aggregated; **separate JSON frame**, sent after `s` when non-zero) |\n| `fr` | server→client | Food **removed** by viewers — `n` count (same rules as `fa`) |\n| `fe` | server→client | Food **eaten** by the worm — `n` count (same rules as `fa`) |\n| `a` | client→server | Add food at `x`, `y` (mm) |\n| `v` | client→server | Remove food near `x`, `y` (mm) |\n| `i` | client→server | Ping |\n\n### State frame (`t` = `s`)\n\n| Key | Meaning |\n|-----|---------|\n| `p` | protocol version (`3`) |\n| `t` | `\"s\"` |\n| `k` | tick (simulation step index) |\n| `r` | plate_radius_mm |\n| `w` | worm_radius_mm |\n| `sm` | Segments: flattened `[x,y,…]` in **nanometres** as integers, `round(mm × 10⁶)`; length `2 × N_BODY_SEGMENTS` (13 segments) |\n| `fm` | Food pellets: flattened pairs in **nm** (same scale as `sm`) |\n| `cm` | Centre of mass `[cx_nm, cy_nm]` in **nm** (same scale) |\n| `Si` | Membrane `S` per PAULA neuron id: `round(float × 10⁴)` (client ÷ `10⁴`) |\n| `Ri` | Dynamic primary threshold `r`, same integer encoding as `Si` |\n| `Fb` | Fired flags: **base64** of a bit-packed byte string; bit *i* (LSB-first within each byte) is `1` if neuron *i* fired (`O \u003e 0`), else `0` |\n\n### Hello (`t` = `h`)\n\n| Key | Meaning |\n|-----|---------|\n| `m` | Short human-readable hint (e.g. protocol summary) |\n| `L` | Optional connectome layout `{ nm, ax, ay }` — Cook A→P as `ax`∈[0,1], D/V heuristic as `ay` (see `connectome_layout.py`) |\n| `M` | Optional list parallel to `L.nm`: per neuron `{ k, ic, ig, oc, og }` — `k` = class (`s` sensory / `m` motor / `i` interneuron / `u` unknown); `ic`/`oc` = in/out chemical synapse degree, `ig`/`og` = gap junction degree (Cook connectome) |\n\n### Food / presence aux frames\n\n- `fa` / `fr` / `fe`: only `p`, `t`, and **`n`** (non-negative integer counts). Emitted **after** the `s` frame for that broadcast tick when any count is non-zero.\n- `u`: `p`, `t`, **`n`** = online viewer count.\n\nBroadcast cadence is **60 Hz** with **latest-state only**: if the sim runs faster, intermediate frames are not queued. If a client send blocks longer than the server timeout, that frame is skipped for that client (the next tick still carries fresh data).\n\n- Client → server examples: `{ \"p\": 3, \"t\": \"a\", \"x\": 1.2, \"y\": -3.4 }`, `{ \"p\": 3, \"t\": \"i\" }`.\n\nThe bundled `web/app.js` can still interpret **legacy v2-style** keys on a state message (`c` / `s` / `f` / `S` / `F` / `R`) if they were ever present; the **server** currently emits **v3** fields above after internal snapshot conversion (`_snapshot_dict_to_wire` in `server.py`).\n\n## Limits\n\n- Global rate limit on food commands (see `server.py`).\n- Max concurrent WebSocket clients (see `server.py`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farterialist%2Flive-c-elegans","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farterialist%2Flive-c-elegans","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farterialist%2Flive-c-elegans/lists"}