dotFly - a fruit fly's brain, simulated in C#
What you see above is a fruit fly flying around a living room, trying to follow a smell cloud, landing on a sugar bowl and extending its proboscis to eat. Which way to turn away from an obstacle, whether the smell is getting stronger or fading, which side the wind blows from, or whether the thing it landed on tastes of sugar is controlled by a complete central nervous system of a real fruit fly - 166,700 neurons and 24.9 million synapses, wired exactly as an electron microscope found them - simulated in real time, in pure C#, on a laptop.
Well… sort of. And this “sort of” is what this post is about.
What is this all hype about?
On September 3, 2026, Google Research and HHMI Janelia published the MaleCNS connectome - the complete wiring diagram of the brain and the “ventral nerve cord” (which carries signals between the brain and the body) of one adult male Drosophila - a fruit fly. Within days the internet did what the internet does: the fly brain was playing Doom, Super Mario 64, jumping over cacti, walking in the browser, and sitting on a Mac desktop.
I got hooked. Just like with dotLLM, I wanted to understand what is really going on there - and for me, with AI, the best way to really understand something is to build it. So I built dotFly: a native .NET “inference engine” of fruit fly-connectome, fast enough to sit inside a Godot game loop. No Python, no native lib wrappers, purely CPU-based.
This post covers: what a connectome actually is and how it was made, what the “fly brain model” everyone runs really is (and even more importantly, what it is not), what all those demos are doing, how dotFly does it in C# with some benchmarks, ending with honest limits of the whole thing.
Wait, is this ethical?
Before we go into the details, a question that pops up immediately: if this is simulating a real brain, should we be doing this at all? Is the simulated fly suffering when it bumps into a wall in Mario? People asked exactly that on r/AskScienceDiscussion and on Hacker News, where one commenter asked: “Is the utility of this program worth a 10% chance that we confidently create perhaps the most cruellest machine possible?”. Do we create a “real living” that we torture in a virtual environment?!
No matter how sci-fi those questions sound, I think it is fair to ask, and I don’t want to dismiss it. But the honest answer requires understanding what is being simulated, and the short version is: it is in some sense simulating the real brain - the wiring is measured from a real animal, neuron by neuron, synapse by synapse - and at the same time it is very far from a living brain. It leaves out much of the brain’s chemistry and detailed ways neurons communicate, and ignores what happens inside each neuron. There are no hormones, no internal state, and no learning. Just kind of “one weight for all 25 million synapses” simplification. The demos “play games” through hand-written adapters that would make any nonlinear dynamical system look like it plays. Nothing in this model establishes that the scanned individual, its memories, or anything resembling experience was preserved.
So no, I don’t think we created “the cruellest experiment possible”. Well, yet. But I also think the discomfort is a useful reminder to be precise about what these demos are - which is what the rest of this post tries to be.
From the basics: what is a connectome?
Three terms are enough to understand everything that follows:
- connectome - the reconstructed “wiring” diagram: which neuron connects to which, with how many synaptic contacts, with what neurotransmitter
- neural model - the equations that make that wiring active (the connectome itself doesn’t compute anything, it’s a graph)
- embodiment - an engineered “interface” between the model and a body, a game, or a world
The internet downloaded that wiring diagram (connectome) and added dynamics and interfaces. It did not download a frozen executable copy of a living fly.
How it was made
It is easy to compress the whole story into “Google mapped a fly brain in September”. The reality is a chain of achievements spanning many years of research:
- 2018 - Zheng et al. publish the first complete electron microscopy volume of an adult female fly brain (the FAFB dataset). Just the images - a ~100 TB stack of slices
- 2024 - the FlyWire consortium publishes the full reconstruction of that brain: ~140,000 neurons, 50 M synapses (Dorkenwald et al., Nature)
- 2024 - Shiu et al. (Nature) publish the first computational model of that whole brain and validate it against real experiments. This is the neural model everybody runs now
- 2025-2026 - MaleCNS: one five-day-old adult male (specimen
Z0720-07m) connectome, brain + neck + ventral nerve cord dissected together. Fixed with aldehydes, stained with heavy metals, embedded in resin, cut by a hot knife into 66 slabs ~20 µm thick, and imaged with enhanced focused-ion-beam SEM at 8×8×8 nm resolution: a gallium ion beam mills away a thin layer, the SEM images the new face, repeat. Seven customized instruments, about 160 teravoxels, ~13 months of imaging. Then Google’s flood-filling networks segmented the neurons, automated detectors found the synapses, a ResNet50 classified the neurotransmitter from EM neighborhoods - and 29 expert proofreaders spent about 44 person-years fixing and annotating it all. Result: 166,700 neurons, 11,710 cell types, 25.6 M directed edges, 124 M synaptic contacts. Published in Cell on September 3, 2026 (Berg, Beckett, Costa, Schlegel et al.), with the v1.0 data available since June.
What it is NOT
A connectome is a “photo” of a dead, sliced, metal-stained brain. It is not an fMRI or an electrode recording of a living brain. It does not measure:
- membrane voltages or any activity at all
- the functional strength of a synapse (only how many contacts there are)
- hormones impact
- anything that was learned, remembered, or felt at that time (well, the fly was dead…)
It is a wiring diagram - a very, very good one - with a predicted neurotransmitter label per neuron.
What are the “inputs” and “outputs”?
If the graph is just 166,700 neurons with IDs, how do we know where the eyes are? This is where a century of Drosophila genetics and neuroanatomy comes in. Each neuron in the model carries an annotation - a cell type (LC4, T4a, MN9, DNp04…), a class, a side - assigned by proofreaders from its morphology, location and connectivity, matched to the literature. And the literature is huge, for example we already know that:
-
LC4neurons are known (from calcium imaging, optogenetics, behavior) to respond to looming objects (objects getting closer/bigger), -
T4a-dto motion in four directions, -
ORN_DM1to a specific odor/smell, - the so-called sugar gustatory receptor neurons (
GRNs) to sugar, -
MN9is the motor neuron that extends the proboscis, -
DNp04and the Giant Fiber are the escape descending neurons (‘outputs’) that go down to the nerve cord.
So, the sensory neurons are the inputs for example: photoreceptors, smell- and taste-sensing neurons, movement- and touch-sensing neurons for the fly’s “antenas”.
And the so-called descending and motor neurons are the outputs: ~1,300 descending neurons carry the brain’s commands to the nerve cord, motor neurons drive muscles. That’s not an assumption of the model - it is the anatomy.
Let’s follow one signal through, the simplest reflex a fly has - something is coming at me, get out:
graph LR
A["a dark spot grows<br/>in the left eye"] --> B["photoreceptors<br/>R1–R6"]
B --> C["lamina, medulla<br/><small>the optic lobe</small>"]
C --> D["LC4 (left)<br/><small>71 neurons:<br/>'something is looming'</small>"]
D --> E["DNp04, DNp02, DNp11,<br/>Giant Fiber<br/><small>descending neurons</small>"]
E --> F["ventral nerve cord<br/>→ wing & leg motor neurons"]
F --> G["take off,<br/>turn away"]
Now the same thing as an experiment. I take the MaleCNS graph, drive the 71 left LC4 neurons with random spikes at 120 Hz for one second - this is what “an object is expanding in the left eye” looks like to the brain - and record who fires. One dotfly run --stimulate type:LC4@L --hz 120 later:
| Neuron | Type | What it is | Rate |
|---|---|---|---|
| 531898 | DNp04 (left) | escape descending neuron | 308 Hz |
| 10010 | DNp01 | the Giant Fiber - the fly’s “jump” command | 276 Hz |
| 10197 | DNp02 | escape descending neuron | 256 Hz |
| 10259 | DNp11 | escape descending neuron | 244 Hz |
| 10752 | DNp03 | escape descending neuron | 220 Hz |
4,792 neurons became active in total (out of 166,700), and the top of the list is exactly the escape pathway the literature describes - on the same side as the stimulated eye. Nobody told the model that LC4 is wired to DNp04. The “wiring” is simply there, and activating the input lights the output. That’s the whole game: activate the inputs, observe the outputs.
The model everybody runs: Shiu et al. 2024
The graph does nothing by itself. Shiu et al. made it run with the simplest thing that could possibly work: every neuron is a leaky integrate-and-fire (LIF) point neuron. Forget the shape of the neuron, its dendrites and ion channels. What is left is a single number, its “membrane potential”. Incoming spikes push it up or down, it leaks back toward a resting value, and when it crosses a threshold the neuron emits a spike of its own and resets. That’s it:
Note how much it takes to fire one: three spikes from a neuron with 90 synaptic contacts, within a few milliseconds - a single spike from a single input neuron does nothing on its own (it would need about 162 contacts). Firing is convergence.
How hard a spike pushes is the only place where the connectome enters the equations. Every edge from neuron $i$ to neuron $j$ has a weight:
\[w_{ij} = \operatorname{sign}(\text{NT}_i) \cdot n_{ij} \cdot w_{syn}\]$n_{ij}$ is the number of synaptic contacts between the two neurons - the microscope counted them. $w_{syn}$ is the same value for all 25 million synapses: 0.275 mV per contact. This is the single calibrated parameter of the whole model. Shiu et al. fitted it so that stimulating the sugar-sensing neurons produces a proboscis-extension response of realistic strength, and left it there. Everything else - time constants, threshold, refractory period, synaptic delay - came from previous electrophysiology.
The $\operatorname{sign}(\text{NT}_i)$ needs a word of explanation for the non-biologists like me. A neuron talks to the next one by releasing a chemical, a neurotransmitter, at the synapse - and each neuron uses (mostly) one kind. Some of those chemicals excite the receiving neuron, pushing its potential up toward the threshold; others inhibit it, pushing it down. The connectome does not measure which chemical a neuron uses either - a classifier predicts it from how the synapse looks in the EM image. The model then reduces the whole chemistry to one bit: in the fly, acetylcholine is the main excitatory transmitter, so
+1. GABA and glutamate are inhibitory, so-1. Three others - dopamine, octopamine, serotonin - are in reality modulators: they change how other synapses behave, they carry things like reward and arousal, and a LIF model has no way to express that. Shiu et al. simply treat them as excitatory,+1. It is a modelling convention, not physiology: assigning+1to a dopamine neuron does not implement reward or learning.
Researchers used the model to make predictions - “if we activate these neurons, those should fire” - and tested 164 of them against real flies with optogenetics and calcium imaging. If paper says 91% agreement - it means exactly that - 91% of those 164 predictions in feeding and grooming circuits. Not 91% of entire fly behavior.
The connectome is fixed - we do not train it
This is the crucial point. The weights are fixed, coming from the number of pairs between each neurons. Nobody trains this network to play Mario, and nobody should: the moment you start fitting the 25M weights to a game score, you no longer have “the fly’s brain” - you have some neural network that happens to have started from a fly-shaped graph. The original model has no plasticity rule at all.
So the whole “game” is, again: activate the inputs, observe the outputs. Drive the sugar neurons with Poisson spikes at 150 Hz, and watch which of the other 127,000 neurons start to fire. Silence one neuron and see what changes. That’s also what makes it valuable - it is a programmable, testable model of neural circuitry, where a hypothesis becomes a run.
So what are all those demos doing?
Every “fly brain plays X” demo, including mine, is three things stacked:
graph LR
W["World / game"] -->|"encoder<br/><small>engineered</small>"| I["Input neurons<br/><small>measured identity</small>"]
I --> B["Connectome + LIF dynamics<br/><small>measured wiring, one calibrated weight</small>"]
B --> O["Readout neurons<br/><small>measured identity</small>"]
O -->|"decoder<br/><small>engineered (fixed or trained)</small>"| W
The wiring and the dynamics are shared. The adapters are where the demos differ, and where much of the apparent intelligence lives: the encoder that turns pixels or some other state into firing rates (fe. of visual neurons - which ones, and how many Hz for what?), and the decoder that turns firing rates (of descending neurons - the brain’s “motor command” cells) into button presses or body motion. Look at a few:
Xenova’s Fruit Fly Simulation - the full 166,700-neuron MaleCNS LIF running in the browser on WebGPU, driving a 3D fly body. You paint stimulation on neurons, or use presets: “walk” stimulates LC9 on both sides, “turn left” stimulates LC9 on the left plus DNa02 on the left, “fly” stimulates LC4 on both sides. LC4 and LC9 are visual projection neurons - each LC type is a detector that reports one kind of visual event to the central brain (LC4: “something is expanding toward me”); DNa02 is a descending neuron that experiments show drives turning. On the output side, the demo reads a handful of descending neurons: DNp09/DNg100/DNg97 for walking speed, DNa02/DNa11/DNg13 left vs right for turning, MDN (the “moonwalker” backing-up neuron) for a jump, DNp01 (the Giant Fiber) for escape flight - each with a hand-chosen gain (8 mm/s, 3.8 rad/s…) documented in its model.json, which I appreciate a lot. One thing to be aware of: the “turn” presets poke the output neuron DNa02 directly, not just the visual input. The author says why - the visual input alone activates DNa02 too weakly - but it means the fly would still turn even if you cut every connection inside the brain. Still, this is the tool that tries to show real wiring activity without heavy adapters around it.
Fly Dino - a different approach altogether. Instead of the whole brain, an 80-neuron subcircuit: 32 visual detector neurons (LC4, LC11, LPLC2… the same “something is coming” family) and 16 descending neurons, picked because in the connectome they are the most strongly wired path from “visual event” to “motor command”, plus the cells in between. Run as a graded rate model (tanh), not Shiu’s spiking LIF. Eight game features (obstacle distance, size, speed… not pixels) are injected into the visual neurons. Then the decoder adapter: the activity of the 16 output neurons goes into a tiny neural network - 243 numbers in total - that picks Run, Jump or Duck. That tiny network was trained by trial and error over 15,680 games; the fly circuit itself never changed. So what’s happening here is that we trained output adapter to interpret some of the wiring activity and translate them into game actions. This is reservoir computing: the brain is used as a fixed, complicated transformation, and only the last “which action” mapping is fitted from training. The nice part is the control experiment: switch the fly circuit off but keep the trained adapter, and the score drops from 2,886 to 41. So the circuit is doing something - it acts as a fixed, nonlinear transformation of the inputs that the small network learned to read.
“Fly Mario”-style demos (Fly64, fly-games) - the full MaleCNS LIF stepped at a coarse time-slices (20 ms), game frames turned into features for the visual detectors (LC4: threat, LPLC2: something approaching, LC10a: something to chase), and on the output side is neuron-to-button mapping: a dozen descending neuron groups - DNa02 left/right for steering, DNp09 for walking forward, and so on - each mapped to a button, with a threshold. In Fly64 that mapping is hand-made (“if left DNa02 fires more than right, press Left”); in fly-games it is fitted by a small linear regression from recorded example play. Reservoir computing again. It predicts the right button 90% of the time on recorded episodes it never saw - and the fly still dies early in live play, because live situations drift away from the recordings. Mario walks into a wall and stays there.
None of this is a criticism. Those are all honest experiments. The point is - the adapters matter enormously, and a moving body proves nothing about what the wiring contributed unless you also run the controls - switch off the connections inside the brain and/or switch off the input.
This leads us to dotFly
dotFly is, first of all, a library: a .NET inference engine for fly-connectome spiking models that Godot, console apps, tests and benchmarks link in-process. It builds memory-mapped checkpoints from the MaleCNS and FlyWire releases, runs the Shiu et al. model on them - exactly (the float64 backend matches Brian2 spike-for-spike) and fast (the float32 SIMD backend runs the whole MaleCNS in real time on a laptop) - and exposes stimuli in, spikes and rates out, recording and replay, and the causal switches. The room demo is a demo: the thing I built to see whether a thin embodiment, with the fly’s own senses still needs a lot of “adapter”.
The room demo
A Godot 4 (.NET) 3D scene: a sunlit living room, a fly, sugar on a table, and “smell clouds” drifting downwind from few places. The MaleCNS connectome runs on dotFly as the only thing between her senses and her actions. Here is what every panel means:
Senses → inputs (encoders, fixed) - two cameras, one per compound eye, render 32×32 luminance images. Per eye, the dark fraction and its growth become an LC4 (looming) rate. Horizontal and vertical image shift become T4a-d (optic flow) rates. Odor concentration at the two antenas becomes an ORN_DM1 rate, wind side a JO-C/E (Johnston’s organ) rate, and sugar contact when landed drives the fly’s taste-sensing neurons in its feet and mouth. 7,524 input neurons in total.
The connectome (measured) - all 166,700 neurons, drawn at their real positions and lit by their real spikes. Nothing here is drawn from a script.
Readouts (measured circuits) - every circuit read is one that I found in the checkpoint with the CLI (dotfly inspect --upstream-of, dotfly run --stimulate), not assumed - 38 neurons in 13 groups:
-
LC4 → DNp04 L/R, Giant Fiber- escape -
T4a → HS,T4b → H2,T4c → VS- optic flow -
ORN_DM1 → DM1 projection neurons- odor amount -
JO-C/E → AMMC012- wind direction -
GRN → MN9- feeding
The decoder (fixed, hand-written) - printed on screen next to the readouts it consumes, and every number in it is a choice:
| Brain signal (readout) | Rule | Behavior |
|---|---|---|
DNp04 left vs right | yaw += 1.2 · (DNp04_L − DNp04_R) | turn away from the looming side |
HS + H2 left vs right | yaw += 0.2 · ((HS_R + H2_L) − (HS_L + H2_R)) | counter-turn against rotation (optomotor) |
HS left vs right | yaw −= 0.3 · (HS_R − HS_L) | center between walls |
DNp01 (Giant Fiber) | speed = 120 + DNp01 body lengths/s | cruise, burst on escape (a real fly: 100-170 BL/s) |
DM1_lPN (odor amount) | > 110 Hz → surge while rising; falling → tumble; lost → U-turn, then casts; > 270 Hz → descend, land | odor search |
AMMC012 left vs right (wind) | while surging: yaw −= 0.6 · (wind_L − wind_R) | fly upwind |
MN9 (smoothed 0.3 s) | > 15 Hz → feed; stops → take off | feeding |
Interesting finding is that smell direction is simply not derivable from any reading (both DM1 candidate neurons respond alike whichever antenna is stimulated), so I was forced to put some “logic” into adapter that the fly searches by flying forward while the smell gets stronger, then turning around when it fades, with the wind signal coming from the brain.
dotFly under the hood: C# performance
dotFly is the sibling of dotLLM in conventions and spirit: a memory-mapped model file, unmanaged state, SIMD kernels, zero allocations on the hot path. Same layered layout, each project a NuGet package:
src/DotFly.Core .dfb checkpoint reader (mmap), graph tables, NeuronModel, RNG, IBackend
src/DotFly.Data MaleCNS / FlyWire release readers
src/DotFly.Cpu SIMD LIF kernel, compute thread pool
src/DotFly.Engine Simulation, input/output ports, real-time driver, recorder, adapters
src/DotFly.Adapters.* ONNX Runtime / ML.NET readout adapters
src/DotFly.Cli dotfly build | inspect | run | bench | explore
And the API demos program against:
// Neurons are addressed by their exact 64-bit IDs from the connectome release (FlyWire root IDs
// or MaleCNS body IDs)
ulong[] sugarGrnIds = [720575940624963786, 720575940630233916, /* 21 sugar GRNs from Shiu et al. */];
const ulong Mn9 = 720575940660219265; // MN9, the proboscis motor neuron (FlyWire v630 root ID)
using Brain brain = Brain.Open(".data/shiu2024/flywire-v630.dfb");
using Simulation sim = brain.CreateSimulation(new SimulationOptions { Seed = 1 });
NeuronSet sugar = brain.ByBodyIds("sugar_GRN_R", sugarGrnIds);
sim.Input(sugar, InputKind.PoissonToV).Fill(150f); // Poisson drive, 150 Hz each
OutputPort mn9 = sim.Output(brain.ByBodyId(Mn9), OutputKind.Rate(50.Ms()));
sim.Run(1.Seconds()); // neural time
Console.WriteLine($"MN9 {mn9.Snapshot.Mean():F1} Hz at {sim.Clock.RealTimeFactor:F1}× real time");
This is the Shiu et al. sugar experiment in ten lines: drive the 21 sugar-sensing gustatory neurons, read the proboscis motor neuron. The full sample (samples/DotFly.Sample.SugarExperiment) runs the paper’s 30 trials plus its silencing experiments and compares with the published results:
flywire-v630: 127,400 neurons, 14,687,178 edges. Sugar GRNs: 21, MN9 index 127193.
30 trials × 1 s in 3.0 s wall (10.1 simulated s per wall s).
Active neurons (mean over trials): 433. Top 10:
720575940621502051 152.3 Hz (sugar GRN)
720575940632425919 150.7 Hz (sugar GRN)
...
MN9 (720575940660219265): 83.3 Hz mean over 30 trials.
Silencing experiments (outgoing synapses zeroed; the neuron still spikes):
silence 720575940621502051: MN9 83.3 Hz (intact 83.3 Hz)
silence 720575940632425919: MN9 78.7 Hz (intact 83.3 Hz)
silence 720575940633143833: MN9 79.6 Hz (intact 83.3 Hz)
Comparison with published results/example/sugarR.parquet (Brian2 2.5.1, 30 trials):
active neurons: 448 vs 433 (Jaccard 0.949); rate correlation 0.9946; spike-count ratio 0.803
MN9: 93.3 Hz vs 83.3 Hz
Thirty one-second trials of a 127,400-neuron brain in three seconds, and the answer the paper got: sugar in, proboscis motor neuron out at ~80 Hz. For populations you normally query by annotation instead - brain.Query(type: "LC4", side: Side.Left), brain.Query(cls: "Kenyon_Cell") - and raw IDs are for reproducing a paper or reading a single named cell. For a game loop, new RealtimeDriver(sim).Start() runs the network on its own threads, the game thread writes InputPorts and reads OutputPort.Snapshot frames lock-free.
Some of the design decisions that made the difference:
- Checkpoint as a memory-mapped file (
.dfb, the GGUF of dotFly) - the graph in Compressed Sparse Row (CSR) form -rowPtr[N+1],col[E],w[E](int16contact counts, signed) - plus annotations, positions and a header recording the data version, filters and sign policy. Building it from the 1 GB Arrow release takes ~10 s, opening it takes milliseconds - Structure of Arrays state in unmanaged, 64-byte-aligned memory -
v[N],g[N]infloat32, a refractory countdown, a ring of 19 spike lists. ~2 MB for the whole MaleCNS - it fits in L2 - The LIF kernel is
Vector256<float>- spike indices compacted withTrailingZeroCount. Kernels are generic overTFloat : IBinaryFloatingPointIeee754<TFloat>sodouble(the reference that matches Brian2 to 1e-11 mV) andfloat(the fast path) share one implementation -
[MethodImpl(MethodImplOptions.AggressiveOptimization)]on every per-step method - the single biggest surprise. Without it the tiered JIT left the kernel and the batch loop at tier-0/OSR for most of a short run: 21 µs/step instead of 6.
Benchmarks
End to end, dotfly bench on my 8-core laptop (Ryzen 7 5800HS, AVX2):
| Scenario | Neurons | Real-time factor | Steps/s |
|---|---|---|---|
| FlyWire v630, 21 sugar GRNs @ 150 Hz (the Shiu experiment) | 127,400 | 19.6× | 196,000 |
| MaleCNS v1.0, no input | 166,700 | 14.5× | 145,000 |
| MaleCNS v1.0, 1,684 T4a neurons @ 150 Hz (0.5 M spikes/s, 0.9 G deliveries/s) | 166,700 | 6.2× | 62,000 |
Python, JavaScript, C#
I couldn’t resist checking how does that compare with what the others run? Three references are easy to pin down. The Python one is Brian2 - the general-purpose spiking simulator the published model runs on. The JavaScript and WebGPU ones are both Xenova’s browser demo: its CPU core, BrainCPU in src/brain.js, is a hundred lines of clean, plain JS implementing the same model - exact LIF integration, dt = 0.1 ms, while BrainGPU runs the same thing as a WGSL compute shader . I ran the JavaScript core in Node 24, and the Xenova’s browser demo in headless Chrome, on the same MaleCNS graph (its own data files, 25.6 M edges) with the same stimulus as dotfly bench, back-to-back on the same laptop. Spike counts agree to within the RNG (534-539 k for the T4a scenario in every implementation), so it is the same work:
| Implementation | Scenario | Steps/s | Real-time factor |
|---|---|---|---|
| Shiu et al. paper, Brian2 (as reported, per CPU thread) | v630, sugar GRNs @ 150 Hz | ~33 | 0.003× |
| Brian2 2.9, numpy target, Python (this laptop) | v630, sugar GRNs @ 150 Hz | ~400 | 0.04× |
Xenova BrainCPU, JavaScript in a Chrome worker, 1 thread | MaleCNS, 1,684 T4a @ 150 Hz | 628 | 0.06× |
Xenova BrainCPU, JavaScript in Node 24, 1 thread | MaleCNS, 1,684 T4a @ 150 Hz | 851 | 0.09× |
Xenova BrainGPU, WebGPU on the integrated Radeon (Vega 8) | MaleCNS, 1,684 T4a @ 150 Hz | 2,409 | 0.24× |
Xenova BrainGPU, WebGPU on the RTX 3050 Ti Laptop GPU | MaleCNS, 1,684 T4a @ 150 Hz | 5,383 | 0.54× |
| dotFly, C#, 1 thread | MaleCNS, 1,684 T4a @ 150 Hz | 10,740 | 1.07× |
| dotFly, C#, 8 threads | MaleCNS, 1,684 T4a @ 150 Hz | 43,656 | 4.37× |
| dotFly, C#, 8 threads | v630, sugar GRNs @ 150 Hz | 196,000 | 19.6× |
Thread for thread, C# is about 11× faster than the JS reference on the same work - not because JavaScript is slow at this (V8 does a fine job on typed arrays), but because of what a JIT cannot give you for free: eight neurons per instruction in Vector256, a state layout that fits in L2, no bounds checks on the scatter, and a delivery loop that touches only its own cache-resident slice of g.
What this table does not include: the demos stepping at dt = 20 ms (Fly64, fly-games). With 200× fewer steps they are cheap by construction, at the cost of no longer being the Shiu model.
Limitations - and where this goes next
Now the honest part. Two big limits of the model itself, which perfectly shows the trade offs between using connectome directcly versus filling the gaps in input/output decoders.
The visual boundary is in the wrong place - today C# computes what the “optic lobe” should compute: the retina encoder turns image shift and expansion directly into T4 and LC4 signals - it skips the first four boxes of the looming diagram shown earlier. A fly’s eye is made of about 800 tiny visual units and a whole optic lobe (a large share of all the neurons in the brain!) whose job is precisely to compute motion and looming from photoreceptor signals. The next milestone is to feed light straight into the photoreceptors and see whether the wiring in between computes motion and looming on its own - if it does, the encoder shrinks to “brightness per eye” and the connectome does the rest; if it doesn’t, the fallback is a hybrid with a Flyvis-style trained optic lobe.
The fly’s own navigation circuit is silent - this is the big one. In the demo, the decision of where to go - surge upwind, tumble, cast, land - is made by my decoder. A real fly makes it in its brain, and that circuit is in the wiring: the neurons that keep track of the fly’s heading, combine it with wind and odor, and drive the steering neuron DNa02 left or right. With wind and odor inputs on, none of it fires. We get a fly that smells and feels the wind, but does not steer. The reason is that this circuit is recurrent - it feeds back on itself. Its activity stays alive only because active neurons keep exciting their neighbours while inhibiting the rest. Current Shiu model is not able to represent this and we need to support more complex “recurrent” neural model.
Closing
Two things are true at once, again. The platform: .NET can run a 166,700-neuron spiking network with 25 M synapses in real time on a laptop, bit-identical across thread counts, matching the scientific reference - Vector256, NativeMemory, memory-mapped files and a hand-rolled thread pool are all you need (🔥). And the science: the connectome is a spectacular measurement, the LIF model is a spectacular starting point, and everything that makes a fly “play a game” today is an adapter that should be shown on screen and switched off in a control run.
dotFly is GPLv3 - the data (MaleCNS, FlyWire) is CC BY 4.0 and downloaded separately. If you are a .NET developer who wants to poke a real brain from C#, or a neuroscientist who wants an inner loop three orders of magnitude faster than the reference, give it a try. Stimulate something. Silence something. Tell me what fires.
- GitHub: github.com/kkokosa/dotFly
- Docs: kkokosa.github.io/dotFly - guides, the room demo, the measured-vs-engineered inventory, findings, CLI and API reference
- References: Berg et al. 2026 (MaleCNS), Shiu et al. 2024, Dorkenwald et al. 2024 (FlyWire), Lappalainen et al. 2024 (Flyvis)
Enjoy Reading This Article?
Here are some more articles you might like to read next: