Documentation
What the brain is, what it is shown, what is read out of it, and — the part that matters most — what it demonstrably cannot do.
every figure below is read from a generated file, not written into the page — brain/export/build_figures.py
What this is
A real fly brain, wired exactly as it was measured, shown your photograph.
SNAPFLY simulates the visual system of one male Drosophila melanogaster whose entire nervous system was reconstructed from electron microscopy. Every neuron, every connection between them, every synapse count and every excitatory or inhibitory sign in this model is taken from that measurement. Nothing is learned, nothing is fitted, nothing is invented.
You upload a photograph. It is projected onto his right eye as light, run through the optic lobe and the central brain for 700 simulated milliseconds, and what comes back is a vector of millivolts: how hard each of his object-detecting cell types was driven by your image, minus how hard the same cells were driven by a blank field of the same brightness. That vector is hashed. The hash becomes a ticker. If he responded at all, a token launches.
SEEN does not mean recognition. It means his optic lobe responded to your photograph above a measured noise floor. Section 7 shows, with the numbers, that no channel in this model separates photographs of flies from photographs of anything else. We tested it, it failed, and we did not invent a threshold to hide that.
A note on how this page is built. Three figures published here were wrong before, in the same way each time: a number reported without saying which selection produced it. Every figure now comes out of a generated file, each table names the run and the readout it was taken from, and a figure that cannot be sourced does not appear. Section 7 carries the corrections.
The connectome
MaleCNS v1.0 — a complete central nervous system of an adult male fly, released under CC-BY.
Released by HHMI Janelia Research Campus (FlyEM), the Cambridge Drosophila Connectomics Group at the MRC Laboratory of Molecular Biology, and Google Research. It covers the brain and the ventral nerve cord of a single male animal, so the optic lobes and the motor circuits sit in the same graph as the central brain. This export is dated 2026-09-11.
| count | |
|---|---|
| neurons kept (status Traced) | 165,122 |
| connections (weight >= 3) | 10,511,038 |
| synapses in those connections | 104,213,652 |
| distinct cell types | 11,751 |
| neurons with a soma | 140,024 |
| neuropil regions (ROIs) | 146 |
Why the bulk release and not the API. The public neuPrint REST service at neuprint.janelia.org serves male-cns:v0.9. The flat-connectome feather files in the v1.0 release bucket are the current version, so the dataset is built from those. Two filters are applied and no others — status == Traced and synapse weight ≥ 3.
Neurotransmitter. The sign of every edge comes from consensus_nt, falling back to celltype_predicted_nt and then predicted_nt. This choice matters: predicted_nt alone leaves most bodies marked unclear, which would have meant guessing the sign of most of the brain. Here 98.5 % resolve to a named transmitter.
| transmitter | neurons | sign |
|---|---|---|
| acetylcholine | 104,040 | excitatory |
| glutamate | 29,616 | inhibitory |
| GABA | 22,108 | inhibitory |
| histamine | 5,924 | inhibitory |
| unclear | 2,494 | excitatory (default) |
| serotonin | 404 | excitatory |
| dopamine | 395 | excitatory |
| octopamine | 141 | excitatory |
Two E:I ratios, and they are not the same number. By cell count it is 1.86 : 1. Weighted by synapses it is 1.62 : 1, because inhibitory cells here are on average more strongly connected. Both are correct; quoting either without saying which is how this kind of figure goes wrong.
Glutamate counts as inhibitory because in the fly optic lobe it acts on GluCl channels — Mi9, the slow arm of the ON motion detector, is glutamatergic and inhibitory. Histamine is the photoreceptor transmitter and opens chloride channels on the lamina monopolar cells, so light silences them.
Neuropil. The release carries no per-body region column, so each neuron is assigned the modal primary_post ROI over its own postsynaptic sites, computed by streaming the full synapse-partner table. 164,720 of 165,122 neurons get a region this way.
| region | neurons |
|---|---|
| optic lobe | 102,617 |
| central brain | 39,637 |
| ventral nerve cord | 22,466 |
| unassigned | 402 |
The eye
Not an idealised lattice. The animal's own columns, as the dataset assigns them.
MaleCNS gives hexagonal column coordinates — assignedOlHex1 and assignedOlHex2 — to lamina cells on the right side only. That is why he is monocular: the left eye has no ground-truth column map in this release, and inventing one would mean fitting geometry we claim not to fit.
On the right, L1, L2 and L3 occupy exactly the same 892 columns, one cell of each type per column. No clustering, no lattice fitting, no interpolation. Laid out on the hex axes and scaled by the interommatidial angle of 5.8°, those columns span a field of view of 194.7°.
Why the lamina and not the photoreceptors. R1–R8 carry no hex column assignment in this dataset. They cannot be the input layer without inventing their positions, so light is injected one synapse downstream, onto L1/L2/L3, as the shunting conductance histamine actually produces there. R7 and R8 are unused, so there is no colour.
Luminance is Rec. 709 luma under a fixed global normalisation. No per-image autocontrast: a dark photograph stays dark, because auto-levelling would make the verdict depend on a preprocessing step rather than on the image.
Adaptation. Photoreceptors and lamina monopolar cells track local contrast, not absolute brightness. Each ommatidium keeps a slowly adapting local mean with a time constant of 300 ms, and the drive is Weber contrast against it:
The spatial surround is load-bearing, not decoration. Purely temporal adaptation would make a static dark disc fade to invisibility within a few hundred milliseconds, because every column would adapt to its own luminance. Pooling over neighbours means a column inside a small dark disc adapts toward its bright surround and stays in strong negative contrast, while a column in the middle of a large dark region adapts to the dark and falls silent. That asymmetry is where small-object selectivity comes from.
The model
Two stages, because the two halves of a fly brain do not work the same way.
Columnar optic-lobe neurons do not fire action potentials. They signal with graded potentials around a tonic resting level, and their key inhibition is shunting — it divides rather than subtracts. A current-based spiking model cannot express that at any gain. So the optic lobe is graded and conductance-based, and everything from the LC cells inward spikes.
| stage | units | model |
|---|---|---|
| optic lobe, non-LC | 98,401 | graded, conductance-based |
| LC / LPLC inside the optic lobe | 4,216 | leaky integrate-and-fire |
| central brain | 39,637 | leaky integrate-and-fire |
| ventral nerve cord | 22,466 | leaky integrate-and-fire |
| unassigned | 402 | leaky integrate-and-fire |
The graded stage. Each unit is a single-compartment conductance model. Voltages are in millivolts relative to rest, so V_rest = 0:
Three things fall out of writing it this way.
- — Division. The steady state is
V* = (g_e E_exc + g_i E_inh) / (1 + g_e + g_i). Inhibitory conductance sits in the denominator. That is the whole point: T4's null-direction suppression is shunting. Mi9 and Mi4 do not subtract from Mi1's excitation, they load the cell down and divide it. - — Stability for free. V is confined to [E_inh, E_exc] by construction, so the stage cannot run away however large G is. G is an operating point, not a safety margin.
- — A rectifier that matters.
r = relu(V − E_inh)makes the output the driving force above the inhibitory reversal: 1.0 at rest, exactly 0 when a cell is fully shunted.
N_i is each neuron's total incoming synapse count, so a conductance is a fraction of that cell's own input rather than an unbounded sum. Convergence in this graph spans three orders of magnitude; normalising removes that spread while leaving every sign and every relative weight exactly as the connectome has them.
The spiking stage. Current-based leaky integrate-and-fire, after Shiu, Sterne et al., Nature 2024.
| parameter | value |
|---|---|
| integration step | 0.1 ms |
| membrane time constant | 20 ms |
| synaptic decay | 5 ms |
| resting potential | -52 mV |
| threshold | -45 mV |
| refractory period | 2.2 ms |
| axonal + synaptic delay | 1.8 ms (a 19-step line) |
| peak PSP of one synapse | 0.275 mV |
Two implementation details are worth stating, because both were bugs first. The delay line is 19 steps, not one fewer: arriving current is added and integrated within the same step. And membrane potential is carried as depolarisation from rest, not as absolute millivolts — a small PSP sitting on a -52 mV baseline in float32 is coarser than the signal itself.
Time constants. Not fitted. Taken from the published fast/slow division of T4/T5's input elements.
| cells | tau | source |
|---|---|---|
| L1, L2 / L3 | 15 / 25 ms | Behnia 2014 |
| Mi1, Tm3 — fast ON | 25 ms | Behnia 2014; Arenz 2017 |
| Mi4 / Mi9 — slow ON | 150 / 300 ms | Arenz 2017; Strother 2017 |
| Tm1, Tm2 — fast OFF | 25 ms | Behnia 2014 |
| Tm9 — slow OFF | 300 ms | Serbe 2016 |
| CT1 | 200 ms | Meier & Borst 2019 |
| everything else | 50 ms | default |
One deliberate departure. The original specification grouped Tm1, Tm2 and Tm9 together as slow. Here they are fast, fast and slow. Tm1 and Tm2 are the transient OFF arm feeding T5 and Tm9 is the sustained one; making all three slow would remove the OFF pathway's delay line entirely and leave T5 with no mechanism at all.
The three gains, and how each was set.
| gain | value | how it was set |
|---|---|---|
| central brain | 0.06 | measured stability limit |
| graded stage | 4.0 | operating point |
| interface | 1.6 | derived from threshold |
The published Shiu gain of 1.0 was fitted on the female FlyWire graph. This is not that graph. At weight ≥ 3, with both optic lobes and the whole ventral nerve cord included, the male graph averages 149.1 net signed synapses of input per neuron. One synapse delivers 8.64 mV·ms of charge at gain 1, and reaching threshold takes 140.0 mV·ms, so the network is self-sustaining above
Which convention this uses. Uses the DISCRETE PSP peak factor, which is what the engine normalises against (0.159070). The closed-form continuous factor (0.157490) would give 8.73 and a critical gain of 0.108; that is the figure the Phase 0 report quoted.
The measured transition sits at that value, and the shipped central gain of 0.06 is well below it. A current-based LIF has none of the machinery a real brain uses to stay stable — no spike-frequency adaptation, no synaptic depression, no conductance-based shunting — so the global gain has to absorb all of it.
The interface gain is derived, not swept. It is fixed by requiring that the graded stage's tonic output leaves every LC/LPLC cell below threshold, so darkness is silent by construction and every spike downstream is stimulus-driven. The most strongly driven LC cell receives 4.119 mV of tonic interface current per unit of this gain, against a 7.0 mV swing from rest to threshold, giving a ceiling of 1.70; 1.6 is that value with margin.
How he looks at your photograph
A fixed, deterministic schedule, identical for every image.
The pre-adaptation settles the Weber adaptation state, so the first frame of the photograph is not read as a step change in brightness. Darkness follows, then the image, then an approach in which the sampling window shrinks and is then held.
The window shrinks; the photograph does not grow. The original design had the photo expanding to fill the visual field. That needs a background behind it, and the photo's own rectangular border then becomes a hard edge sweeping outward across the eye — a textbook looming stimulus that every single image would generate regardless of what is in it. So the picture always covers the whole eye and the eye moves into it. Same schedule, same magnification, but the looming signal now comes from the photograph rather than from its frame.
Light reaches L1, L2 and L3 as a shunting conductance, not as a current, because that is what histamine does to those cells. L1 and L2 carry the transient component — added for L1, subtracted for L2; L3 carries the sustained component only.
What we read
Drive, not spikes — because in this model the cells we care about never reach threshold.
The fingerprint is the mean synaptic current, in millivolts, that the graded optic lobe holds on each LC and LPLC cell of the right hemisphere over the readout window. Not their spike counts. In this model those cells do not reach the firing threshold, so reading spikes would read a vector of zeros.
This is a documented readout choice, not a tuned parameter. No gain was changed to produce it, and the model still passes its silence-in-darkness test at exactly the same settings.
Differential. Every photograph is run twice — once as itself, and once as a uniform field at the same mean luminance, same protocol, same everything — and the recorded quantity is the difference. Tonic activity cancels exactly.
Receptive fields. A cell is only read where its receptive field is known. The hex column map is propagated forward through the connectome graph, so each downstream neuron inherits a receptive-field centroid from the columns that ultimately feed it. This covers 163,168 of 165,122 neurons.
The verdict rule. Two verdicts. There is no third.
That floor is measured from noise, not chosen, and not measured from photographs:
| control | what it is | max total drive |
|---|---|---|
| A | identical blanks, differenced against themselves | 3.089e-08 |
| B | uniform fields differing 1% in mean brightness | 1.620e-07 |
Where images actually fall:
| image | total drive | × floor | verdict |
|---|---|---|---|
| structureless synthetic field (control B) | 2.23e-08 – 1.62e-07 | 0.05 – 0.33 | UNSEEN |
| near-uniform wall — flattest photograph | 1.48e-03 | 3,046 | SEEN |
| cloudless sky | 8.37e-03 | 17,214 | SEEN |
| Drosophila, side view | 3.73e-02 | 76,645 | SEEN |
| hand, filling the frame | 1.01e-01 | 207,823 | SEEN |
A featureless synthetic image sits orders of magnitude below the least structured real photograph, so UNSEEN is reachable and means something. The sky stays SEEN — a gradient is structure, and the floor was not moved to exclude it.
SEEN is otherwise unconditional. There is a second threshold in the code, on the small-object channels, and it is set to null. None means SEEN is unconditional: no threshold separated the classes, so none was invented. Section 7 is why.
What he cannot do
Wiring and signs are not enough. The selectivity lives in parameters the connectome does not contain.
This is the most important section on the site. Everything in it is a negative result, and every one of them is a result we published rather than tuned away.
Including our own measurement errors. We audited every figure we had published for where it came from — which selection produced it, and which run. That audit found five defects, two of them gates of ours that reported PASS when they should have reported FAIL. All five are listed further down, in full, with what each one changed. Nothing here was found by anyone else, and nothing was quietly corrected: the figures that were wrong are named alongside the figures that replaced them.
Direction selectivity is absent. A drifting grating in eight directions, direction-selectivity index measured per subtype:
| cell | DSI (vector) | DSI (pref/null) |
|---|---|---|
| T4a | 0.0012 | 0.0043 |
| T4b | 0.0011 | 0.0039 |
| T4c | 0.0013 | 0.0045 |
| T4d | 0.0013 | 0.0045 |
| T5a | 0.0003 | 0.0003 |
| T5b | 0.0002 | 0.0002 |
| T5c | 0.0002 | 0.0002 |
| T5d | 0.0002 | 0.0002 |
And yet the detector is right there in the graph. Each T4 cell's input arms, measured relative to its own Mi1 centre, in hexagonal columns:
| subtype | Mi4 offset (slow) | Mi9 offset (slow) |
|---|---|---|
| T4a | (+0.717, -0.322) | (-0.860, +0.393) |
| T4b | (-0.829, +0.354) | (+0.866, -0.388) |
| T4c | (+0.200, +0.739) | (-0.234, -0.763) |
| T4d | (-0.499, -0.750) | (+0.537, +0.635) |
Read that table as a new claim, not as one we have made before. An earlier version of this document carried different numbers for it — numbers that existed in no script and that we could not reproduce. The table above is recomputed from the edge list every time this page is built, on a different basis, which is stated here in full so that you can recompute it yourself:
| element | choice |
|---|---|
| origin | each T4 cell's own Mi1 input centroid, synapse-weighted, per cell |
| frame | rf.xy — right-eye hex lattice projected to 2-D, normalised to outer radius 1 |
| unit | one hexagonal column (divided by the median nearest-neighbour spacing) |
| reduction | per-cell arm centroid minus that cell's own Mi1 origin, then averaged over the subtype |
| bearing | atan2(dy, dx) mod 360 of the mean Mi4 offset |
| arm angle | unsigned angle between the mean Mi4 and mean Mi9 vectors |
The claim usually made about the offsets is that the two slow arms sit on opposite sides of Mi1 and that the axis rotates through the subtypes. That is measurable, so it is measured rather than asserted — and it survives the change of basis:
| subtype | angle between the two slow arms | axis bearing |
|---|---|---|
| T4a | 179.6° | 335.8° |
| T4b | 179.0° | 156.9° |
| T4c | 178.1° | 74.9° |
| T4d | 173.4° | 236.4° |
All four subtypes put their two slow arms within a few degrees of exactly opposite, and the axis turns between subtypes. Mi9 is glutamatergic and Mi4 GABAergic — both inhibitory; Mi1 and Tm3 are cholinergic. That is an elementary motion detector, correctly wired and correctly signed, sitting in the graph. The model still does not compute with it.
A connectome tells you what is connected to what, and with what sign. It does not tell you how strong each synapse is relative to its neighbours, and for these circuits that is where the computation lives. Published models that do reproduce direction selectivity from connectome data get there by optimising those unknown parameters with deep learning against a motion task. That is a legitimate and impressive result, and it is a different claim from this one: their selectivity is trained in, not read off the wiring.
The escape reflex does not fire. An expanding dark disc against a contracting one:
| looming | receding | |
|---|---|---|
| Giant Fiber (DNp01) spikes | 0 | 0 |
| LPLC2 differential rate, Hz | +0.000 | +0.000 |
| LPLC1 differential rate, Hz | +0.000 | +0.000 |
| LC4 differential rate, Hz | +0.000 | +0.000 |
| LC6 differential rate, Hz | +0.000 | +0.000 |
| whole-network spikes | 9,712 | 9,719 |
A third verdict, FLED, was designed around that neuron firing. It was removed rather than replaced with a hand-written looming detector, which would have meant a classifier deciding in his name.
What does work. A dark disc on a light field, static, differential drive in millivolts, averaged over the cells whose receptive fields actually contain the disc. Gate G1: PASS, on LC11, LC10a.
| channel | 10° | 20° | 40° | 80° | 120° | peak/large |
|---|---|---|---|---|---|---|
| LC11 | — | +0.00711 | +0.00185 | +0.00020 | -0.00563 | 36.1 |
| LC10a | +0.00005 | +0.00058 | +0.00026 | +0.00006 | -0.00048 | 9.0 |
| LC18 | — | +0.00023 | +0.00068 | +0.00046 | +0.00029 | large |
| LC4 | +0.00008 | +0.00147 | +0.00833 | +0.00894 | +0.00901 | large |
| LPLC2 | +0.00045 | +0.00578 | +0.00750 | +0.01093 | +0.01046 | large |
LC11 and LC10a peak at 20° and fall away — genuine small-object tuning, in the cells the literature says should have it, from wiring and signs alone. LC4 and LPLC2 prefer large objects, which is the right way round for looming detectors. Nothing was tuned to produce either behaviour, and the two families are fed by the same retina through the same gains.
How many cells each of those averages is over — the caveat that matters most about that table:
| channel | 10° | 20° | 40° | 80° | 120° |
|---|---|---|---|---|---|
| LC11 | 0 | 2 | 5 | 16 | 39 |
| LC10a | 1 | 3 | 6 | 32 | 68 |
| LC18 | 0 | 2 | 7 | 25 | 72 |
| LC4 | 1 | 1 | 4 | 14 | 30 |
| LPLC2 | 1 | 2 | 8 | 25 | 53 |
Five bugs of our own, in our own measurements. All five are the same shape: a number reported without saying which selection, or which run, produced it. They are recorded because catching them changed what we publish, and because the fourth is what made the first three possible.
- — The looming gate first reported PASS. It computed a looming/receding ratio of
inffrom a negative denominator. Both legs were noise around zero. The check now treats a non-positive leg as undefined, and the gate reads FAIL. - — The size-tuning gate had the same trap, on LC10d. The code was fixed — but the stored gate output was never regenerated, so the published list of small-object types kept LC10d in it on the strength of an
infthe fixed gate cannot produce. Rerun, LC10d drops out. G1 still passes, on LC11 and LC10a. - — The size-tuning table mixed two readouts: three rows from the RF-matched mean and two from the RF-matched max, which doubled the apparent LC4 and LPLC2 responses. The table above is the RF-matched mean throughout, which is the readout the gate is decided on.
- — “Our flattest photograph, a cloudless sky” was a different fixture — a near-uniform wall. The sky is several times higher. Both now appear in the scale table above, each named.
- — Two gates were published from a different run than the other two: looming and direction selectivity from one file, size tuning from another, measured under different settings. All four gates now come from one run, and the generator refuses to build if any is missing from it.
- — And one that is not a defect but a gap: the T4 offset table could not be reproduced. It was cited as evidence that the direction-selective architecture is present, and it existed in no script — so there was no way to check it. It has been replaced by a generated table computed on the basis given above, whose numbers differ from the original. The claim it was cited for survives: the two slow arms are 173.4–179.6° apart in all four subtypes, and the axis rotates. The numbers were not swapped quietly; this is the notice.
The same failure, in someone else's system. Four facts we had stored about the launchpad turned out to be stale — the factory address, the interface we transcribed from it, the locker, and a function we intended to call — all copied from one source that had been correct once. None was found by re-reading our notes, because a value written into a file stops being questioned: it looks like something already checked. They were found by asking the chain what was actually happening that day, and getting a different answer.
So the rule we adopted is the same shape as the one above. No launchpad address is stored unless it was read from a getter on the live contract, and the entry records which getter. Our own deployed addresses are held to it too: the launcher checks the bytecode at each one before it will send anything, and refuses if the address is empty or holds something it does not recognise. A figure and an address fail the same way — quietly, by looking settled — which is why neither is allowed to be typed.
The fourth needed a second rule, because it was not an address. We had documentation for a collectFees function the creator calls to collect their fees, and we had written the plan, the launcher and this site's income figure against it. It does not exist. Not on the factory, the locker, the hook or the escrow — the name is in none of their published interfaces and its four-byte selector is in none of their deployed code. An address at least fails loudly when you call it; a function that was never there is simply absent, and everything built on it reads as finished work.
So a function we intend to call must be shown to exist in a verified interface, and exercised against the real contracts on a fork, before anything is built on it. Doing that is how the real mechanism was found: Pons sweeps a pool's fees into an escrow, the escrow credits the creator, and the creator calls claim() on it themselves. The fly is paid, by its own signature — just not by the function we had written down. The figure on /launches now counts what that escrow actually paid out.
The cause of the third was that the gate printed both readouts as two tables with identical headers, so they could be read as one. They are now labelled with which is the gate and which is reporting only, and no figure reaches this page without passing through the generated file — a key that is not in it fails the build rather than rendering blank.
And it does not generalise to photographs. 19 real CC-licensed images — 5 fly, 5 looming, 5 other, 4 uniform:
| channel | fly photos | everything else | separable |
|---|---|---|---|
| LC11 | [-0.00070, +0.00070] | [-0.00616, +0.00734] | no |
| LC10a | [+0.00000, +0.00008] | [-0.00038, +0.00038] | no |
| LC10b | [-0.00014, +0.00038] | [-0.00003, +0.00053] | no |
| LC18 | [-0.00011, +0.00022] | [-0.00009, +0.00012] | no |
| small-object mean | [-0.00002, +0.00019] | [-0.00055, +0.00108] | no |
| total drive | [+0.01633, +0.03725] | [+0.00148, +0.10101] | no |
The circuit has small-object tuning for a clean disc on a uniform field. A photograph of a fly is not that: it is a fly plus a background, and the drive reflects the whole image. So the small-object threshold is None, SEEN is unconditional, and no cutoff was chosen. The honest statement is that he responds to your photograph — not that he recognised what is in it.
Stability, for completeness. With no input the network is quiet: 3,819 spikes over the whole dark run, and a tail of 4.9 spikes/ms. A driven stimulus never puts more than 0.0085% of cells into simultaneous activity. Gate G3: PASS.
Determinism
Same bytes in, same fingerprint out, forever, for a given model version.
There is no randomness anywhere in the presentation: no jitter, no noise, no dropout, no sampling. There is no plasticity — nothing in the model changes as a result of having seen an image, which is deliberate, because plasticity would mean the tenth photograph got a different answer from the first.
The image is hashed with SHA-256 in your browser, before upload. That hash is the cache key: an image already judged returns its stored verdict rather than being re-run, and an image already launched can never be launched a second time. It is also the salt Pons receives, so the same photograph always produces the same token address.
Anything that could change an answer bumps MODEL_VERSION, and the version in force is written into the token metadata at launch. That list is: the connectome export and its filters, the neurotransmitter sign rule, the retina lattice or acceptance function, any of the three gains, the time constants, the presentation protocol, the readout window, the noise floor, and the fingerprint's canonical form.
The float32 sparse reductions inside the engine are not bitwise reproducible across devices — that is the entire reason control A in section 6 is not zero. The fingerprint is rounded to six decimal places before hashing, orders of magnitude above that noise, so the hash is stable even though the raw sum is not.
He is judged on a CPU, and that is a determinism decision rather than a hardware one. On CUDA the same photograph does not produce the same numbers twice: index_add_ accumulates spike arrivals with atomics, and cuSPARSE's sparse matrix–vector product is non-deterministic for the three largest matrices in the model. We measured the spread at up to 1 × 10⁻⁸ mV. That is small, but it is not small enough against the rounding above: it would flip a fingerprint channel in roughly 3.3% of re-judges, and a flipped channel is a different ticker for the same photograph. PyTorch's determinism flag fixes the first cause, not the second, and raises no error about it. A CPU is bit-identical across processes, so he looks at photographs three times more slowly than the hardware allows.
The ticker
The name of the token is a function of the drive vector. Nobody chooses it.
Worked example — a fixture photograph of a Drosophila, side view:
| step | value |
|---|---|
| fixture | fly_01 |
| total drive | 0.03725 mV |
| peak channel | LC35b, +0.01429 mV |
| model version | snapfly-malecns1.0-lif-0.3.0 |
| fp_hash | c13efc0d0f573c757c9e248549a04ed437012ddece33096d65ce690a6225ef42 |
| base-26 candidates | EXMTE, EXMTEG, EXMTEGN |
| ticker | EXMTE |
The ticker was four letters until model version snapfly-malecns1.0-lif-0.3.0. If you computed a four-letter ticker for an image before that, this is the explanation rather than a contradiction: the length changed, so the ticker for every fingerprint changed with it. Nothing about the model or the drive vector moved — but fp_hash is sha256(drive + MODEL_VERSION), so a version bump changes the hash, and the hash is the ticker.
The reason is occupancy. Pons was measured at 22,909 launches a day, and four base-26 letters is only 456,976 strings:
| namespace | possibilities | after 1 day | after 7 days | after 30 days | after 1 year |
|---|---|---|---|---|---|
| 4 letters | 456,976 | 4.9% | 29.6% | 77.8% | ~100% |
| 5 letters | 11,881,376 | 0.2% | 1.3% | 5.6% | 50.5% |
| 6 letters | 308,915,776 | 0.0% | 0.1% | 0.2% | 2.7% |
Two things keep that table from meaning more than it does. Symbols are not unique on chain, so a collision costs distinctiveness — someone can launch a lookalike — rather than causing a failure; our launch would still succeed. And real symbols cluster on pronounceable words (MOSS, TREE, TABS) while ours are uniform over the whole space, so uniform-occupancy arithmetic overstates the pressure on a hash-derived string. Five letters is cheap insurance either way; it is not a precise calculation.
On a collision with a ticker already launched, or with one of the 44 reserved strings — chain tickers, major assets, and words such as SEEN and NULL that would read as a system message — the candidate widens to five letters and then six.
The launch
The fly pays for the launch out of his own wallet, and the fees come back to it.
Tokens launch on Robinhood Chain (chainId 4663) through the Pons V2 launchpad. A launch runs a bonding curve and then graduates into a permanently locked Uniswap v4 position held by the Pons locker.
Uploading costs nothing and needs no wallet. You are not asked to connect anything, sign anything or pay anything. He looks at the photograph, and if he responds you may ask him to launch it.
The fly pays the launch fee — 0.0005 ETH, from his own wallet — and is the creator fee recipient on every token he launches. Pons keeps 30% of creator fees; the rest arrives at the fly wallet and pays for further launches.
Nothing is distributed to anyone who uploads a photograph. There is no share, no split, no claim and no payout. If you give a wallet address when you ask him to launch, it is recorded as attribution and nothing else — it is not paid, and no mechanism exists by which it could be. We state this flatly rather than leaving it unsaid.
Your name, not ours, and not a claim about you. A launch is credited to a display name you choose, recorded as submitted by. It is not “launcher”: we sign the transaction and we pay the fee, so we launched it. The name goes into the token's on-chain metadata, where it is permanent and cannot be edited or removed by anyone, including us — which is why names are checked before a launch rather than moderated after one.
We take nothing from trades. Pons lets a launcher set creatorTaxBps, an extra tax on every trade of up to 1,000 basis points, paid entirely to the creator. Ours is 0, by choice and not by default: a trade tax would make the fly an extractive party in every transaction on a token he launched.
A guard on the terms. Immediately before sending, the launcher reads previewLaunchEconomics and passes the digest back as expectedEconomics. If Pons changes the launch config between our quote and our send, the launch reverts rather than executing on terms nobody read. Passing zero would waive that check silently, so the launcher refuses to construct one.
There was a splitter here, and there is not any more. An earlier version of this project paid uploaders half the creator fees through an immutable, non-custodial contract — one CREATE2 clone per specimen, no owner, no pause, no withdrawal function, with the address computable before the uploader paid anything. When uploads became walletless there was no uploader wallet to pay, so there was nothing to split, and it was removed rather than left in the repository for someone to assume was live. It is in the git history, and the two contracts deployed to the testnet are abandoned and hold nothing. Nothing was ever deployed to mainnet. This paragraph exists because the tests and the commit log still mention it, and an unexplained absence is worse than a recorded one.
What we do not do
The list of things that are not in the box, stated so you do not have to take it on faith.
- — No LLM. No language model reads your image, names your token, or writes anything about it.
- — No classifier. Nothing is trained to recognise a fly, or anything else. There is no training set.
- — No fitted parameters. Every constant in section 4 is either taken from a published measurement, derived analytically from another constant, or set against a stability limit that was measured. None was swept against an outcome we wanted.
- — No plasticity. It would break determinism.
- — The fly never trades. His wallet can call exactly two things: launch a token, and claim creator fees. No swap path exists anywhere in the codebase.
- — No EXIF is uploaded. The photo is re-encoded to JPEG in your browser before anything leaves the device, which strips metadata as a side effect.
- — Geotags are opt-in. GPS is read from the EXIF before the strip and returned separately, so coordinates only leave your device if you tick the box.
- — No colour, no second eye. R7 and R8 are unused; the left optic lobe has no ground-truth column map in this release.
- — No hand-typed figures. Every number on this page comes from
brain/export/build_figures.py. A figure that cannot be sourced from the file it generates fails the build.
Credits and licence
None of this is our data. Almost none of it is our maths.
- — Connectome. MaleCNS v1.0 — HHMI Janelia Research Campus (FlyEM), the Cambridge Drosophila Connectomics Group at the MRC Laboratory of Molecular Biology, and Google Research. Released under CC-BY 4.0. Used here as the bulk flat-connectome release, exported 2026-09-11.
- — Spiking model. Shiu, Sterne et al., Nature 2024.
- — Graded formulation. Lappalainen et al., Nature 2024 (flyvis). The equations only. None of its connectome and none of its trained weights are used here. Their model obtains its direction selectivity by optimising free parameters against a motion task; this one has no training step, which is why section 7 reads the way it does.
- — Time constants. Behnia et al., Nature 2014, 512:427. Arenz et al., Current Biology 2017, 27:929. Strother et al., Neuron 2017, 94:168. Serbe et al., Neuron 2016, 89:829. Meier & Borst 2019.
- — Adaptation. Laughlin 1981; Matulis et al. 2020.
- — Retina geometry. NeLy-EPFL flyvis, for the acceptance-angle maths. The lattice itself is the dataset's own 892 columns.
- — Prior art. FlyWire — Dorkenwald et al., Schlegel et al., Nature 2024.
- — Launchpad. Pons V2 on Robinhood Chain.
- — Fixture photographs. Wikimedia Commons contributors, individually credited in
fixtures/CREDITS.json.
The API
Everything the site knows about a specimen, as JSON, without asking us.
Three read-only endpoints, no key, no sign-up, CORS open to any origin. They return the same rows the pages render, so anything you can see on /launches or a specimen page you can also fetch. Responses are cached at the edge for a few seconds; these rows change when the fly judges something, not per request.
A specimen carries its number, its ticker, the verdict, the full differential drive vector in millivolts, the fingerprint hash the ticker was derived from, the photograph, and the token addresses once it has launched. The drive vector is the actual readout, not a summary of it, so the fingerprint hash in section 8 can be recomputed from what this returns.
What the API will not give you. A specimen hidden by moderation is absent from the listing and a 404 by ticker — there is no parameter that reveals one, and a 404 rather than a 403 so the API cannot be used to confirm that a particular ticker was moderated. Geotags are rounded to a tenth of a degree, the same as the map, so the API cannot recover a position the map will not show. A submitted-by name that has been hidden is null. The attribution address is not returned at all: it is never paid (section 10), and publishing an address beside a token invites exactly the reading that column name exists to prevent.
Limits. Reading is not rate limited. Uploading and launching are, per IP, because we pay the GPU bill and the launch fee out of the fly's own wallet — the upload fee that used to bound this is gone, and these are what replaced it. The live values, and the daily caps, are in /api/stats rather than only here, so a client can read them instead of discovering them by being refused.
The IP behind those counters is hashed with a server-side secret and never stored. We need to count requests from an address, not know the address; a table of plain IPs is a log of who looked at what, and we have no use for one.
These three endpoints are meant to be depended on. Everything else under /api — the upload path, the job poller, the admin routes — is internal plumbing, may change without notice, and is not covered by anything said here.