I wanted to build something between a toy animation and a full neuroscience simulator. The result is FlySim: a small, deterministic model based on three recognizable parts of the Drosophila nervous system. It runs entirely in JavaScript and exposes enough internal state to show why the fly turns, lands, or refuses a flower.

One world, four smells

The world contains four flower species. Each one produces a different pattern across 16 odor channels. Two species contain nectar and two are toxic. The fly starts without that information. It has to approach flowers, experience the result, and change its response to that odor.

This keeps the task small enough to inspect. The interesting part is not whether the fly eventually finds nectar. It is whether a bad landing changes the next decision in a way that can be traced through the model.

Animated circuit map: flower odor passes through the antennal lobe, mushroom body, learned valence, and behavior, while the central-complex compass supplies a drifting heading estimate.
Odor identity, learned valence, and an intentionally drifting compass all feed the same behavior loop.
01Antennal lobe

Turns raw odor concentration into a stable identity signal.

02Mushroom body

Creates a sparse odor code and learns value from reward or punishment.

03Central complex

Maintains an internal heading using angular velocity alone.

04Behavior loop

Chooses search, track, feed, or avoid from the circuit outputs.

Keeping odor identity stable

A fly moving toward a flower sees the same odor at very different concentrations. If the input is used directly, getting closer can swamp the pattern that identifies the flower. FlySim uses divisive normalization in the antennal lobe:

PNᵢ = ORNᵢ¹·⁵ / (σ¹·⁵ + (Σ ORN)¹·⁵)

The shared denominator suppresses overall magnitude while preserving the relative pattern across glomeruli. The steering loop can still use the difference between the left and right antennae, while the mushroom body receives a more stable odor identity.

Sparse memory, then depression-only learning

The mushroom body maps 16 projection-neuron values into 800 Kenyon cells. Each Kenyon cell samples four inputs. An APL-style global inhibition step keeps only the strongest 40 responses, so each odor activates a small subset of the population.

That sparse code feeds two opposing outputs: approach and avoid. Both start with equal weights, which makes the fly naive rather than optimistic or fearful. A reward depresses the avoid pathway for the active Kenyon cells. A toxic landing depresses the approach pathway. There is no separate strengthening rule.

The behavioral effect matters more than the weight update. Once the learned valence for an odor becomes negative, the fly does not merely report a lower score in the HUD. It steers down the gradient and can refuse to land.

A compass that is allowed to be wrong

The central-complex model is a 32-unit ring attractor. It moves its activity bump from angular velocity and never receives the fly's true heading. This produces dead reckoning, including accumulated error.

I left that drift visible. The HUD shows body heading, compass heading, and the difference between them. Hiding the mismatch would make the demo cleaner, but it would also remove one of the most useful signs that the compass is an internal estimate rather than a value copied from the renderer.

The renderer cannot change the experiment

The brain and world advance at a fixed 200 Hz. Rendering runs independently. A slow frame can change how smooth the motion looks, but it does not change the time step used by the circuits.

The renderer is also read-only. It does not call the simulation's random-number generator. That detail prevents a camera or graphics change from silently moving every flower and changing the fly's experience. The same simulation state drives the map, chase, and point-of-view modes.

What to watch in the demo

  1. Start at 4× or 16× speed and watch the behavior label switch between SEARCH and TRACK.
  2. After a toxic landing, look at the learned-value row for that flower species.
  3. Watch for AVOID and the refused counter as the fly encounters the odor again.
  4. Use V to move between map, chase, and fly point-of-view modes.
  5. Press F to swap nectar and toxin. The old policy is now wrong, so the circuit has to adapt.

The portfolio route uses the standalone build: one HTML file, no framework, no WebGL dependency, and no network calls after the page loads. The 3D view is a small software renderer drawn onto a 240-pixel canvas.

Why I built it

The project started from the question behind the Fruit Fly Brain Observatory: what becomes easier to understand when neural structure is something you can run rather than only read about?

FlySim is deliberately smaller. Its value is that the entire path from odor to action fits in a browser tab, and every number shown in the interface belongs to the same running model.

Run the simulation Read the code on GitHub