# PoopFly **A whole-MaleCNS spiking fly brain. It decides for itself when to poop.** A housefly eats, builds up gut pressure, *feels* it through interoceptive neurons, and the simulated brain decides to find the toilet and defecate. Relief lowers pressure, which produces a homeostatic reward (dopamine-like) that drives **three-factor synaptic plasticity** — so over trials the fly gets faster at pooping when full, and learns which background song it prefers. There is no `if pressure > threshold: poop()`. The action is read out of **motor-population firing rates** on the real connectome, in a Web Worker, in your browser.

Python Node React TypeScript Three.js tests

![PoopFly running: the fly perched on the toilet in New York, the live motor-population readout, the music panel and the brain view](docs/media/poopfly.gif) *The real UI at `partial_100000` — 100,000 neurons, 19,716,276 synapses. The fly picks `EAT` here from its own motor readout while the brain view shows the spiking network behind it.* --- ## Quick start Prereqs: **Python 3.11** and **Node 20**. ```bash pip install -r pipeline/requirements.txt # one-shot bootstrap: build the graphs if missing, validate, then start the app .\run.ps1 # Windows ``` Or step by step: ```bash python pipeline/preprocess.py --modes debug,core # -> webapp/public/data/ python pipeline/validate_runtime.py --mode core # prove the export is self-consistent cd webapp npm install npm run dev # http://localhost:5173 ``` ```bash npm test # 25 tests: LIF kernel, graph integrity, the closed behaviour loop npm run build # production bundle ``` `full` and the 100k/50k/10k subsets are built with the benchmark ladder: ```bash python pipeline/preprocess.py --modes bench ``` --- ## The question this answers *Did the fly poop because the simulated nervous system produced the behaviour, or because the programmer told it to?* The code path and the test suite are built so the answer is the former. Interoceptive pressure becomes a **real Poisson spike train** injected into real neurons; those spikes propagate through **real measured synapses**; whichever motor population fires hardest *is* the action. Ablate a region, cut dopamine, disable plasticity — behaviour changes because the **network** changed, not because a branch in the code did. The renderer is a **view**. It never produces activity. --- ## Data verification & neuron counts (read first) The `.feather` files in this folder are the **official Janelia/GCS MaleCNS v1.0 release**, verified byte-for-byte: the provided `connectome-weights` MD5 matches the published object exactly. An earlier version of this project misread the official file as corrupt — that reading was wrong, and this section replaces it. What the official files actually contain (measured): | | Value | Note | |---|---:|---| | `connectome-weights` rows | **151,856,684** | documented as "connection strengths for all segments in the dataset" | | segments with T-bars | **1,834,661** | every `body_pre` is one of these | | distinct `body_post` segments | **87,576,984** | most are unannotated fragments | | edges between two T-bar segments | **32,751,675** | the graph this pipeline exports | | official annotated neurons | **166,700** | `body-annotations` rows with a `superclass` (matches FlyWire Codex) | | neurons with ≥1 validated edge | **165,650** | the `core` graph | - The official connectome-weights is the **full segment graph, not a neuron-only graph** — it includes unmerged fragments, glia and other unannotated segments (which appear in the official `tbar` and `syn-partners` tables too). - `body-annotations` and `body-neurotransmitters` **are present** here (from the official [download page](https://male-cns.janelia.org/download/)); the pipeline reads `body-annotations` (`superclass` present) as the authoritative neuron set. **How the pipeline handles it (no silent discarding):** edges are kept only when *both* endpoints are segments of the T-bar set; a body counts as a neuron only with the official `superclass` annotation. Everything filtered is reported per mode — see `webapp/public/data/build_summary.json` and the **Integrity** tab in the app. ### Exported graphs (measured, never theoretical) | mode | neurons | connections | on disk | |---|---:|---:|---:| | `debug` | 3,000 | 340,857 | 7 MB | | `partial_10000` | 10,000 | 1,741,608 | 35 MB | | `partial_50000` | 50,000 | 11,103,313 | 224 MB | | `partial_100000` — fast interactive subset | 100,000 | 19,716,276 | 399 MB | | **`core`** ← default — the genuine MaleCNS neuron connectome | **165,650** | **25,552,591** | **518 MB** | | `full` — all segments (not only neurons) | **1,745,204** | **32,751,675** | 730 MB | `core` reproduces the published edge count (25.55M ≈ 25.6M) — it *is* the annotated MaleCNS neuron graph: 165,650 of the 166,700 official neurons have at least one edge at minconf 0.5. `full` is every segment in the edge list, **not** an all-neuron graph. The app always shows **measured** counts. **Which mode to use:** `core` is the default — the complete annotated neuron connectome (165,650 of the 166,700 official neurons). `partial_100000` is the lighter, faster interactive subset and a single click away in the dataset picker. `full` carries the most bodies but is sparsely wired and sits near 0.03× real time; it is a validation scale, not an interactive one. --- ## How the loop works ``` PHYSIOLOGY ENCODING CONNECTOME (Web Worker) ┌───────────────┐ ┌────────────────────┐ ┌──────────────────────────────┐ │ gut content │ │ PRESSURE │ │ LIF spiking network │ │ gut pressure │─────▶│ HUNGER │─────▶│ on the real MaleCNS graph │ │ energy │ │ MUSIC_A|B|C │ │ typed-array kernel │ │ position │ │ (Poisson drive) │ │ │ └───────────────┘ └────────────────────┘ │ motor populations → rates │ ▲ └──────────────┬───────────────┘ │ │ │ winner-take-all decode │ ▼ │ dopamine ◀── homeostatic improvement ◀── chosen action └──────────────────────────────────────────────────────────┘ ``` 1. **Drive** — gut pressure / hunger / context become Poisson spike rates onto dedicated real neuron pools. 2. **Propagate** — spikes cross the real connectome. Synaptic current is *leaky* (`tauSyn`), so convergent input summated across the synaptic window is what brings a neuron to threshold. 3. **Decide** — eleven motor populations (`MOVE_*`, `STOP`, `EAT`, `DEFECATE`, `PLAY_MUSIC`, `SONG_*`) are read out as firing rates. The strongest one above threshold wins. 4. **Consequence** — the environment applies the action physically, then reports homeostatic improvement → reward → dopamine. 5. **Learn** — three-factor plasticity (eligibility × dopamine) changes real synapse weights, which changes what the fly does next time. Exploration is the one deliberately non-neural term: behavioural variability so the fly can *discover* a rewarded action before reinforcement takes over. --- ## Layout ``` pipeline/ Python 3.11 — raw feathers to runtime graphs inspect_data.py schema / row inspection of the raw feathers preprocess.py feather -> CSR/CSC runtime graphs + integrity report validate_runtime.py proves exported graphs are self-consistent synapse_detail.py on-demand per-neuron synapse JSON (streams 13 GB safely) malecns_common.py shared constants / body-id handling docs/ ARCHITECTURE.md data flow, sparse representation, the kernel, the loop ASSUMPTIONS.md strict real-vs-modelled split, and what this does NOT claim webapp/ Vite + React + TypeScript + Three.js (see webapp/README.md) src/sim/ protocol, LIF kernel, env (physiology), populations, agent, worker src/three/ BrainView (spike cloud), ToiletScene (3D stage) src/components/ vitals, behaviour, music, learning, experiments, debugger, benchmark public/data// generated runtime files (git-ignored) tests/ vitest run.ps1 one-shot dev bootstrap ``` Runtime files per mode: `graph.{indptr,indices,weights}`, `graph_r.*` (reverse, for the debugger), `neurons.{bodyid,nt,region,pos,deg}`, `manifest.json`. --- ## What is real vs. what is modelled Full detail in **[docs/ASSUMPTIONS.md](docs/ASSUMPTIONS.md)**. In short: **Real** — taken from the provided MaleCNS files, never overwritten: - neuron identities (body IDs, mapped 1:1 to runtime indices) - directed connectivity and the immutable `baseW` synapse counts - neurotransmitter predictions (`tbar-neurotransmitters`) - anatomy / neuropil labels (80 regions) and per-synapse compartments **Modelled** — explicit, configurable, documented: - the LIF point-neuron model and all its parameters (`DEFAULT_CONFIG.lif`) - synaptic kinetics, transmission delay, and the NT → sign mapping - input encoding, the choice of drive/motor populations, and output readout - physiology, homeostatic reward, dopamine, music valence - plasticity rule, and the per-dataset excitability calibration Every parameter is live-editable in the app. Change the membrane time constant and the fly's behaviour changes because the *simulation* changed. The simulation is **deterministic** given the same graph, seed and parameters (plasticity off): all randomness uses a seeded PRNG, and the test suite asserts identical spike counts across runs. **What this does not claim:** it is not a molecular or biophysical reconstruction, the LIF neurons do not reproduce real ion-channel dynamics, and nothing here is presented as proof of how a real fly's brain works. --- ## Tests ```bash cd webapp && npm test ``` ``` ✓ tests/kernel.test.ts (7) LIF dynamics, delays, plasticity, ablation, lazy reverse graph ✓ tests/graph.test.ts (10) CSR/CSC integrity, reversed-graph mapping ✓ tests/poopfly.test.ts (9) closed loop: neural decisions, reward, determinism ``` The behavioural tests assert the *causal chain*, not just outcomes — forcing a motor population is what produces the action, and high gut pressure alone never hardcodes `DEFECATE`. --- ## Deployment The app is 771 KB; the connectome is 1.8 GB. Deploying means deciding where that data lives. - **Vercel + object storage** — app on Vercel, graphs on Cloudflare R2, pointed at with `VITE_DATA_BASE`. Full walkthrough in **[webapp/README.md](webapp/README.md#deployment-vercel)**. - **GitHub-only** — `partial_100000` (399 MB, largest file 79 MB) fits GitHub's 100 MB per-file limit and Vercel's limits better than the other modes. `core` and `full` have 102–131 MB files and are **rejected** by GitHub. The app probes which manifests actually exist at startup and only offers those modes, so hosting any subset works without code changes.