# neurogenesis-sim: the whole documentation in one file

This file is generated by `scripts/build_context.py` from the repository's documentation, for AI assistants and
readers who want the full picture without browsing the repository. Section 1 is generated from the code and the docs
(state, open work); the rest is the documentation as published, in the order of the site. Code blocks are real
commands and APIs; claims about results are about simulations. Repository: https://github.com/jobellet/neurogenesis-sim


# 1. Project status (generated)

- Commit `ba72f05` of 2026-11-04; file generated 2026-11-04.
- Registered developmental stages (32): `apoptosis`, `arbor_synapses`, `assign_receptors`, `cell_cycle_clock`, `choice_point`, `cluster_boutons`, `competitive_elimination`, `connect_by_hand`, `critical_period_gate`, `differentiate`, `express`, `express_receptors`, `fasciculate`, `freeze`, `grow_arbors`, `grow_axons`, `migrate`, `mosaic`, `myelinate`, `partner_select`, `pharmacology`, `place_by_hand`, `proliferate`, `prune`, `refine_by_activity`, `scale`, `seed_cells`, `set_transmitters`, `sort`, `temporal_competence`, `tissue_expand`, `transporter_switch`.
- Test functions: 514 in `tests/`.

## Open pull requests

- none

## Not done yet (collected from the roadmap, the stage reference and the PLACEHOLDER notes)

- (roadmap) Development: `BodyPlan` (regions, projections), one fixed growth order (placement → sign → differentiation → identity → axons), rule registry in 5 phases (placement, shift, target, probability, weight), knockouts, tonic currents; geometry, fields, `Tissue`, stage schedule (A1–A5, B1–B5, C1–C13, D2, D3; see `docs/STAGES.md`): chunked growth (D4), and the parts of C8, D1 and D2 marked partial below
- (roadmap) C8: partial: guidance rules as a stage; dense only up to 2048 cells per side, sparse cell-list search above (only three probability rules there, see D2); errors on missing guidance; `expression_gradient` target rule (target map from named graded expression columns, per axis gain and sign, min-max or raw levels; dense and sparse)
- (roadmap) B5 (Field operations): partial: `Tissue.cycling` (bool, per cell; `True` for `seed_cells` / `proliferate`, `False` for `place_by_hand`; carried by `add_cells` / `compact` / `subset` / `concatenate` / `to_network` / `from_network`; `proliferate` divides only cycling cells) done; `SparseConnectivity.plastic` (bool, per synapse; None = all plastic; carried by `take` / `concat` / `append` / `compact` / `subset` / `concatenate` and, in memory only, by `to_network` / `from_network`; `refine_by_activity`, `prune`, `scale` and `neuromodulate` leave non-plastic synapses untouched) done; `contacts` (C19) done.
- (roadmap) C19: partial: `contacts` and weight scaling done; `post_node` placement waits for N2/N3
- (roadmap) C18, C20: not done
- (roadmap) D2 (Sparse guidance): partial: `sparse_guidance` with an exact (dense) mode and a cell-list mode supporting three probability rules (`identity_expression`, `no_autapse`, `source_sign`); `grow_axons` uses the cell-list mode by default above 2048 cells per side (#44). The sparse mode draws different random numbers from the dense one, and the legacy `grow()` stays dense (fingerprints).
- (roadmap) K4b (Longer delays in the developmental stages): partial (#72): `grow_axons` (and its streamed variant), `connect_by_hand`, `myelinate`, `dendritic_attenuate` and `freeze` use the `max_delay` Param, default the storage bound of 255 ticks (`MAX_DELAY_TICKS`), and warn only when clipping. Still clipped at 31: `arbor_synapses` (`neurogenesis/stages/arbors.py`) and the connectome importer (`neurogenesis/data/importer.py`).
- (roadmap) J8 (Gene-expression atlases as fields): partial: annotation atlases as geometries (`neurogenesis.data.atlas`: NRRD / MetaImage volumes, structure ontologies with descendants, `structure_geometry` → `VoxelMaskGeometry`, `fit_to_box`), used by `examples/atlas_relay_mask.py` for the retina → relay example, run on the real P4 annotation of the Allen Developing Mouse Brain Atlas (manifest `examples/datasets/allen-devmouse-p4.json`; run 3 of the retina → relay use case, relay shaped by p2A because the P4 3D annotation does not delineate the dLGN). Gene-expression grids as fields and registration between ages are not done.
- (docs/background/literature.md) A comparison table with measured numbers (time to grow and simulate a reference circuit) against Cx3D / BioDynaMo, Brian 2 and NEST; a BibTeX file of all references; and short summaries of how each listed model maps onto toolbox stages and Params.
- (docs/changelog.md) Versioned release notes generated from tagged releases.
- (docs/citing.md) Software citation (Zenodo DOI per release, `CITATION.cff`) and the paper describing the toolbox, with BibTeX.
- (docs/faq.md) Questions collected from users once the repository is public.
- (docs/getting-started/installation.md) Download links to the release page, checksums, and screenshots of the installer on each platform, once the first release is tagged.
- (docs/getting-started/installation.md) `pip install neurogenesis-sim` from PyPI and a conda-forge package, once the package is published.
- (docs/integration/bridge.md) Small client libraries (`neurogenesis-bridge` for Node and Julia), and an example notebook driving the bridge from MATLAB.
- (docs/integration/cluster.md) A complete cluster recipe (SLURM and a cloud alternative): environment setup, GPU selection, checkpoint / resume of evolution, gathering islands, and measured scaling of simulate_counts with the number of networks.
- (docs/integration/file-formats.md) Further formats (roadmap phase J): NIR, PyNN scripts, NeuroML 2 / LEMS (Gleeson et al. 2010), gene-expression atlas grids as fields, NWB / neo recordings as fitting targets.
- (docs/integration/other-simulators.md) Cross-simulator checks (J6): the same grown networks simulated in the toolbox and, through SONATA and PyNN, in NEST and Brian 2, with spike statistics compared against declared tolerances.
- (docs/reference/cli.md) Planned: `neurogenesis run`, `neurogenesis stages --json`, `neurogenesis data list`.
- (docs/tutorials/cli.md) Planned commands: `neurogenesis run project.json` (run a Project's schedule and save the network), `neurogenesis stages --json` and `neurogenesis ui-schema` (catalogue of stages and Params, task H3), `neurogenesis validate module.json` (re-run a module's assays, task E4), `neurogenesis data fetch` (connectome datasets, task I1). An animated terminal recording (asciinema) of this tutorial.
- (docs/tutorials/desktop-apps.md) Screenshots of each app (network view, inspector, live development preview, schedule editor generated from the UI schema, evolution progress), a GIF of growing and simulating a project end to end, and installation instructions for the released packages.
- (docs/tutorials/fields-geometry.md) Figures: a gallery of geometries (sampled points), a layer stack painted on a curved sheet, and a pinwheel map with its charges; a GIF of cells migrating along a gradient until a stop field. Tutorial for painting fields in the desktop apps' field painter.
- (docs/tutorials/genomes-evolution.md) Tutorial on multi-island runs on a cluster (SLURM array jobs), checkpointing and resuming evolution, and a gallery of evolved modules with their audit reports.
- (docs/tutorials/index.md) Jupyter notebook versions of every tutorial (rendered with myst-nb, runnable on Binder / Colab), and a video walkthrough of tutorials 1–4.
- (docs/tutorials/modules.md) A module registry (roadmap G1): `neurogenesis install <module>`, semantic versions, compatibility with toolbox versions, and a browsable gallery of published modules with their validation reports and screenshots.
- (docs/tutorials/neuron-models-cost.md) A table of measured throughput on reference hardware (laptop CPU, workstation GPU, Apple silicon) and a "which model can I afford?" interactive calculator.
- (docs/tutorials/plasticity.md) Figures: the STDP window measured from the implementation; weight distributions before and after refinement by spontaneous waves; a GIF of receptive fields sharpening during activity-dependent refinement.
- (docs/tutorials/rules-plugins.md) A cookiecutter template for a plugin / module repository (package layout, tests, CI, docs) and a community index of published plugins.
- (docs/tutorials/schedules.md) A full worked example — a layered sheet grown from progenitors (seed → proliferate → migrate into layers → differentiate by layer → grow axons → refine by spontaneous waves → prune) — once the fix wave is merged (roadmap milestone M2, example G2.3), with a GIF of every stage.
- (docs/tutorials/viewer.md) A GIF of the viewer simulating a network while the camera orbits (`docs/_scripts/viewer_capture.py` writes it, but headless software-WebGL capture was too heavy for the documentation build machine; capture it on a desktop with a GPU).
- (docs/tutorials/viewer.md) A hosted web demo on GitHub Pages (the built viewer with preset networks), embedded in this page.
- (docs/use-cases/connectome-constrained.md) The full example once phases I and P are merged: dataset download, the imported subset in the viewer, the grown circuit next to it, and the comparison of their type-level statistics.
- (docs/use-cases/developmental-hypotheses.md) A worked knockout study on the topographic-map example (which genes are necessary for topography, measured across held-out seeds), with figures.
- (docs/use-cases/shared-modules.md) A worked example module (an ON / OFF sensory stage) with its capacity test, evolution logs, audit report and screenshots, published in its own repository and composed with the topographic-map example.
- (docs/use-cases/teaching.md) Ready-to-use course material: a 2-hour practical with exercises and solutions, slides, and a hosted web demo that needs no installation.
- (docs/use-cases/topographic-map.md) A quantitative study: precision vs sigma and number of map cells, across 100 development seeds, with and without activity-dependent refinement.
- (docs/STAGES.md) Not implemented: the inside-out layering test case as a test of `migrate` alone.
- (docs/STAGES.md) Not implemented: pruning inside the stage (follow with `prune`), other neuron models.
- (docs/STAGES.md) Not implemented: the legacy `receptor_sign` rule is not re-expressed as a profile.


# README

## neurogenesis-sim

**Grow spiking neural networks the way nervous systems are built, by hand, by genes, or both, and simulate them
cheaply.**
### Documentation
**<https://jobellet.github.io/neurogenesis-sim/>**: tutorials, use cases, integration guides, stage and API
reference, related work. **For AI assistants:** the whole documentation, with a generated status section (what
exists, what is not done, open pull requests), as one Markdown file to paste or upload into a chat:
[neurogenesis-sim-context.md](https://jobellet.github.io/neurogenesis-sim/neurogenesis-sim-context.md) (~70k tokens)
or the short [neurogenesis-sim-brief.md](https://jobellet.github.io/neurogenesis-sim/neurogenesis-sim-brief.md)
(~11k tokens); `python scripts/build_context.py` makes them locally. The site is built from `docs/` by `.github/workflows/docs.yml`; to build it locally:
`pip install -r docs/requirements.txt && sphinx-build -b html docs docs/_build/html`. Key files:
- `docs/ROADMAP.md`: target architecture (geometry, fields, tissue, stages, parameter sources, GUI), design rules and
  the task list.
- `AGENTS.md`: rules for contributors and agents (determinism, backward compatibility, low compute, tests).
- `docs/EXTENDING.md`: rules, plugins, knockouts, tonic currents, module projects.
- `docs/STAGES.md`: the registered stages, geometries, fields and sparse guidance: parameters, defaults, limits.
- `docs/NEURON_MODELS.md`: neuron models, units, cost estimates.
- `CONTRIBUTING.md`: code and module contributions.
- `docs/THIRD_PARTY.md`: software used and the literature behind each mechanism.
  
neurogenesis-sim is a general toolbox for building circuits through development. You describe where tissue is (2D or 3D
shapes) and what is already known about it (layers, areas, maps, cell positions). You then choose a sequence of
developmental stages (cell division, migration, differentiation, axon growth, pruning, activity-dependent refinement
...), and for every parameter of every stage you decide whether it is:
- set **by hand**;
- read from a **hand-drawn field** (for example a layer boundary or an orientation map);
- controlled by **genes** (and then evolved);
- or **fitted to data**.

The result is an integer leaky integrate-and-fire network, or a more detailed one, that can be simulated, measured,
evolved and shared as a module.

| Workflow | What you specify | What development does |
|---|---|---|
| Supervised (by hand) | geometry, cells, connections or connection rules | compiles and simulates exactly what you specified |
| Unsupervised (genetic) | a genome and a schedule of stages | genes control every stage; evolution searches genomes that work for every developmental seed |
| Semi-supervised | a hand-made scaffold (e.g. cortical layers, cytochrome-oxidase blobs, pinwheel centres) plus a schedule | genes and fitted parameters work on top of the scaffold |

The toolbox makes no assumption about species, organ, task or environment. Projects that use it define their own
geometries, stages, stimuli and objectives, and can publish working circuits (for example a retina with ON/OFF ganglion
cells) as modules that others reuse as-is.

**Status (2026-11-02).** Implemented and tested: the spiking kernels and neuron models, cost estimates, genomes and
evolution, modules with a registry and validation assays, geometries and fields, a configurable schedule of developmental
stages (`docs/STAGES.md`), supervised fitting (CMA-ES), connectome data import, pharmacology profiles, branching arbors
with SWC I/O, SONATA export / import, the bridge, the browser viewer and the macOS app. In progress: the Windows / Linux
app (H5). Planned: the remaining stages of the second wave (C18, C19, C20), further export formats (J3–J5: NIR, PyNN,
NeuroML 2), and lifting the remaining 31-tick delay clip in arbor synapses and the connectome importer (K4b; other
stages accept delays up to 255 ticks). Configurable stages are
young and their API may still change. Everything open is in `docs/ROADMAP.md` (tasks for parallel development, rules in
`AGENTS.md`); the generated status section of the AI context file (`scripts/build_context.py`) lists it in one place.

### What exists today
| Piece | Module | What it is |
|---|---|---|
| Spiking kernel | `neurogenesis.spiking` | Integer LIF neurons (bit-shift leak and synaptic decay, per-neuron threshold, refractory period, Poisson drivers, bursters, axonal delays up to 255 ticks through a multi-word spike history, neuron indices up to 2³², an array-backed synapse store). Identical spikes in Python (`reference`), JAX (`jax_backend`, CPU or GPU), the browser and the C library `nglif`. `batch.simulate_counts` runs many networks on sequences of stimuli. |
| Neuron models | `neurogenesis.spiking.models` | `lif_int` (default), `two_compartment` (soma + dendrite) and `hh` (Hodgkin–Huxley), switchable per simulation on the same network, sharing synapses, delays and drivers. See `docs/NEURON_MODELS.md`. |
| Cost estimates | `neurogenesis.cost` | Memory and time per model before running, calibrated on the actual machine (`python -m neurogenesis estimate --neurons 3e8 --synapses-per-neuron 5000`). Every simulation refuses networks that cannot fit. |
| Body plans and growth | `neurogenesis.development` | Regions (fixed or grown placement, roles, imposed sign) and allowed projections; one growth procedure: placement and sign → lateral-inhibition differentiation → molecular identity → axon guidance. Deterministic per development seed; parallel growth. |
| Developmental rules | `neurogenesis.development.rules` | Extension points in 5 phases. **placement:** mosaics. **guidance shift / target / probability:** identity-dependent targets and receptor expression, point-to-point topography, targets read from graded expression, laminar targeting, no autapse, sign-specific projections. **weight:** sign-inverting receptors. Each rule is inactive unless its gene is present; plugins travel with plans. |
| Knockouts, tonic currents | `neurogenesis.development` | `knockout(genome, regions, projections)`; standing currents per cell type (`genome["tonic"]`). |
| Genomes | `neurogenesis.genome` | Nested dicts; genes addressed by paths (into arrays too), coded in [0, 1] for evolution, grouped by mechanism, saved as JSON. |
| Evolution | `neurogenesis.evolution` | Island model with fresh development seeds each generation; choice on validation seeds; audits on held-out seeds (fitness = worst + 0.25 × mean − cost). |
| Modules | `neurogenesis.modules` | `Module` = plan + genome + inputs / outputs + validation + provenance + neuron model + version, saved as JSON; `compose` assembles modules (prefixed names, each keeps its genes, plugins and rules merged); a local registry (`python -m neurogenesis install`, `registry.get`). |
| Parameter profiles | `neurogenesis.profiles` | Default parameters for a species, region or age with the status of every number (`measured`, `derived`, `assumed`, `free`), its source and what was read; `extends` for species and region layers; `python -m neurogenesis profile check --all`. The toolbox never depends on a profile: any value can be a `Gene`, `Field` or `Fit`. |
| Validation | `neurogenesis.validation` | Declarative assays stored in modules (stimuli per region, readout, metric, gate, knockouts), rerun by `python -m neurogenesis validate module.json`. |
| Fitting | `neurogenesis.fitting`, `neurogenesis.evolution.semi_supervised` | `Fit` params fitted to data targets (cell counts, densities, layer thickness, connection statistics, firing rates) with CMA-ES over several development seeds (`python -m neurogenesis fit`); genes evolved with an inner fitting loop; Pareto evolution. |
| Development schedule | `neurogenesis.stages`, `neurogenesis.devcost` | A Project's ordered stages (seed, cell_cycle_clock, proliferate, apoptosis, tissue_expand, migrate, sort, mosaic, express, temporal_competence, differentiate, critical_period_gate, grow_axons, fasciculate, choice_point, partner_select, place / connect by hand, prune, scale, refine_by_activity, competitive_elimination, myelinate, freeze), each parameter a `Param`; cost per stage before running (`python -m neurogenesis estimate --project p.json`). Chunked growth streamed to disk for very large tissues (`neurogenesis.streaming`). See `docs/STAGES.md`. |
| Arbors, pharmacology, data | `neurogenesis.arbors`, `neurogenesis.pharmacology`, `neurogenesis.data` | Branching arbors (SWC I/O, `grow_arbors`, synapses with path-length delays); transmitter / receptor profiles compiled to signed weights by developmental context, and a per-cell chloride reversal potential that switches with maturation (`transporter_switch`); connectome manifests, verified downloads and streaming import (`python -m neurogenesis data fetch`); volume-transmitted neuromodulation (`neurogenesis.neuromodulation`) and passive dendritic attenuation (`neurogenesis.dendritic`). |
| Geometry and fields | `neurogenesis.geometry`, `neurogenesis.fields` | Primitives, CSG, meshes and voxel masks (`.npz`); grid, analytic, vector, label, layer-stack and pinwheel fields, gradients and distances; every field usable as a `Param`. |
| Interoperability | `neurogenesis.interop` | Export / import with a report of what is exact, approximated or dropped: SONATA (`python -m neurogenesis export net.json --format sonata --out dir`), exact round trip; one shared translation of the integer neuron into a current-based exponential LIF. |
| Bridge and apps | `neurogenesis.bridge`, `desktop/` | JSON-lines server (`python -m neurogenesis.bridge`), NGB1 binary networks, UI schema export (`python -m neurogenesis ui-schema`); native macOS app; Windows / Linux app in progress. |
| Analysis | `neurogenesis.analysis` | Frequency–current curves (from rest), mosaic regularity. |
| Viewer | `viz/` | Browser 3D viewer and sandbox (three.js): imports network JSON, shows neurons by role and sign, rasters, per-neuron inspection and editing. |

### Install and try
```
pip install -e ".[test]"                    # Python >= 3.10: numpy, jax (CPU; "jax[cuda12]" for GPU), matplotlib
cd viz && npm ci && npm run build:kernel && cd ..   # browser kernel, for the equivalence tests
JAX_PLATFORMS=cpu pytest -q
python examples/topographic_map.py --cpu    # a sensor sheet -> map circuit grown from a genome
python examples/hand_built_network.py      # supervised: every cell and synapse by hand
python examples/hand_drawn_column.py --cpu # semi-supervised: hand-drawn layers, genes choose cell types
python examples/pinwheel_sheet.py --cpu    # semi-supervised: density follows hand-placed pinwheels
python examples/evolved_development.py     # unsupervised: a genetic schedule evolved on a toy objective
python -m neurogenesis estimate --neurons 1e5 --synapses-per-neuron 1000 --seconds 1
cd viz && npm run dev                       # viewer at http://localhost:5173; import example_out/network.json
```

```python
from neurogenesis.development import BodyPlan, FixedPlacement, Region, grow
from neurogenesis.spiking.batch import simulate_counts
from neurogenesis.modules import Module

plan = BodyPlan("demo", regions=[Region("sensor", (0, 0, 0), (30, 30, 5), FixedPlacement(labels), role=1),
                                 Region("map", (50, 0, 0), (30, 30, 10))], projections=[("sensor", "map")])
net = grow(plan, genome, seed=0)                              # an SNNNetwork; net.save_json("network.json")
counts = simulate_counts([net], [currents], ticks=100, model="lif_int")   # or "two_compartment", "hh"
Module("demo", plan, genome, inputs=["sensor"], outputs=["map"]).save("demo.module.json")
```

### Continuous integration
GitHub Actions (`.github/workflows/ci.yml`), on every push:
- every test on Python 3.10 and 3.12;
- the browser kernel compiled, so that the Python / JAX / browser equivalence tests run and are never skipped;
- fixed CPU performance benchmarks on main, reported against the previous main run on the same machine type (report only; a regression above 30% is flagged);
- the cost estimator from the command line;
- the viewer type-checked and built.

### Reproducibility
- Same plan + genome + development seed → the same network, bit for bit.
- Same network + kernel seed → the same `lif_int` spikes in Python, JAX and the browser. Float models are deterministic
  per platform.
- Projects that depend on the toolbox should freeze fingerprints of the networks their results come from, and test them.

### Citing
See `CITATION.cff` (GitHub's "Cite this repository" button), and cite the methods and datasets you use
(`docs/THIRD_PARTY.md`).

### Licence
MIT (`LICENSE`). Third-party software and data keep their own licences (`docs/THIRD_PARTY.md`).


# Page: getting-started/installation.md

## Installation

### Desktop apps (no Python needed)
The native apps bundle their own Python runtime and the toolbox:

| Platform | Package | Status |
|---|---|---|
| macOS (Apple silicon and Intel) | `.dmg` | in progress (roadmap task H4) |
| Windows 10 / 11 | setup `.exe` and portable `.zip` | in progress (task H5) |
| Linux | AppImage | in progress (task H5) |

> **PLACEHOLDER.** Download links to the release page, checksums, and screenshots of the installer on each platform, once the first release is tagged.

### Python package
Requirements: Python ≥ 3.10, numpy, jax, matplotlib (installed automatically).

```bash
git clone https://github.com/jobellet/neurogenesis-sim.git
cd neurogenesis-sim
pip install -e ".[test]"
```

For GPU simulation, install the CUDA build of JAX instead of the CPU one:

```bash
pip install -U "jax[cuda12]"
```

> **PLACEHOLDER.** `pip install neurogenesis-sim` from PyPI and a conda-forge package, once the package is published.

### Browser viewer
The viewer is a static web app (TypeScript + three.js) in `viz/`:

```bash
cd viz
npm ci
npm run build:kernel   # the browser spiking kernel, also needed by the Python equivalence tests
npm run dev            # http://localhost:5173
```

### Checking the installation
```bash
JAX_PLATFORMS=cpu pytest -q
python -m neurogenesis estimate --neurons 1e4 --synapses-per-neuron 100 --seconds 1
```

All tests should pass. The equivalence tests between the Python, JAX and browser kernels need
`npm run build:kernel`; the C kernel tests need a C compiler and CMake (see `desktop/common/native/nglif/README.md`).

### Building this documentation
```bash
pip install -r docs/requirements.txt
sphinx-build -b html docs docs/_build/html
```
The figures and animations are produced by `docs/_scripts/make_media.py` from real runs of the toolbox.


# Page: getting-started/quickstart.md

## Quickstart (5 minutes)

### 1. A network from the command line
```bash
python -m neurogenesis generate --neurons 500 --morphology column --weight-scale 0.2 --refractory 3 --out column.json
python -m neurogenesis simulate --network column.json --ticks 1000 --raster raster.png
python -m neurogenesis stats --network column.json
```

*[Figure: The raster written by `simulate --raster` (one tick = 1 ms).]*

### 2. Grow a circuit from a genome (Python)
The example below grows a sensor sheet that projects topographically onto a map, stimulates two opposite corners of
the sheet and checks where the map responds.

```bash
python examples/topographic_map.py --cpu
```

```text
stimulus at corner (0, 0): 4 map neurons fire, spike-weighted mean u of the map 0.09
stimulus at corner (1, 1): 7 map neurons fire, spike-weighted mean u of the map 0.74
wrote example_out/network.json and example_out/module.json
```

`u` is the position along the map's first axis (0 to 1). In this simulation, stimulating the corner (0, 0) of the
sheet activates the near side of the map, and the opposite corner the far side. The core of the script:

```python
import numpy as np
from neurogenesis.development import BodyPlan, FixedPlacement, Region, grow, region_of
from neurogenesis.spiking.batch import simulate_counts

side = 6
sheet = [[(i + 0.5) / side, (j + 0.5) / side, 0.5] for i in range(side) for j in range(side)]
plan = BodyPlan(name="topographic map", regions=[
    Region("sensor", origin=(0, 0, 0), size=(30, 30, 5), placement=FixedPlacement(sheet), role=1),
    Region("map", origin=(50, 0, 0), size=(30, 30, 10))],
    projections=[("sensor", "map")])

genome = dict(count=dict(map=200), inh_weight_factor=2.0, w_clip=2047,
              migration=dict(map=dict(mean=np.full(3, 0.5), spread=0.35)),
              inhibitory=dict(map=dict(a0=-6.0, a=np.zeros(3))),
              guidance={("sensor", "map"): dict(B=np.diag([2.0, 2.0, 0.0]), c=np.zeros(3),
                                                 sigma=0.12, p0=0.9, w=900.0)})

net = grow(plan, genome, seed=0)                  # deterministic for a given seed
sensor = region_of(net, plan, "sensor")
ext = np.zeros((1, net.num_neurons), np.int32)
ext[0, sensor[0]] = 120                           # constant current into one sensor neuron
counts = simulate_counts([net], [ext], ticks=50)[0]
net.save_json("network.json")
```

### 3. Look at it
```bash
cd viz && npm run dev
```
Open <http://localhost:5173>, click **Import** and choose `example_out/network.json`.

*[Figure: The browser viewer with an imported network.]*

### 4. Know the cost before scaling up
```bash
python -m neurogenesis estimate --neurons 1e5 --synapses-per-neuron 1000 --seconds 1
```
```text
100,000 neurons, 100,000,000 synapses, 1 simulated s (1,000 ticks of 1 ms)
  lif_int          fits (memory 2.5 GB of 66.8 GB available); about 24.7 min for 1000 ticks (24.7 min per simulated second)
  two_compartment  fits (memory 2.5 GB of 66.8 GB available); about 27.5 min for 1000 ticks (27.5 min per simulated second)
  hh               fits (memory 2.5 GB of 66.8 GB available); about 24.7 min for 1000 ticks (24.7 min per simulated second)
```
(Output on a 64-GB CPU node; your numbers depend on your machine.)

### Next steps
- concepts — the vocabulary: plans, genomes, stages, Params, tissues, modules.
- index — step-by-step tutorials.


# Page: getting-started/concepts.md

## Core concepts

```text
Project (JSON + .npz, versioned schema)
├── Geometry          2D / 3D shapes: primitives, meshes, voxel masks
├── Fields            hand-made or generated data on the geometry (gradients, layers, maps, point sets)
├── Genome            nested dict of genes (numbers addressed by paths), evolvable
├── Schedule          ordered list of stages; each stage = operator + parameters (Params)
└── Plan              the original BodyPlan, still usable (compiled into a default schedule)

Tissue  (struct of arrays that stages transform: positions, lineage, types, expression, signs, connectivity)
   │  developmental stages: seed_cells, proliferate, apoptosis, migrate, sort, express, differentiate,
   │                         grow_axons, prune, scale, myelinate, refine_by_activity, ...
   ▼
SNNNetwork  →  simulate (lif_int | two_compartment | hh)  →  spikes, rates, metrics  →  fitness / fit / module
```

### Body plan
A `BodyPlan` lists **regions** (boxes in space with a placement rule, a role —
internal, input or output — and an optional imposed sign) and the **projections** allowed between them. `grow(plan,
genome, seed)` runs the fixed growth procedure: placement and sign → lateral-inhibition differentiation → molecular
identity → axon guidance. Same plan + genome + seed → the same network, bit for bit.

### Genome and genes
A genome is a nested dictionary of numbers and arrays. A **gene** is a path into it plus bounds, for example
`(("guidance", ("sensor", "map"), "sigma"), 0.02, 0.5)`. Genes are coded in [0, 1] for evolution
(`to_unit`, `from_unit`) and saved as JSON
(projection keys become `"src->dst"`). Gene groups (`guidance_genes`, `body_genes`, ...) collect the genes of a
mechanism.

### Rules
Rules are small functions in five phases (placement, guidance shift / target / probability, weight) that modify the
growth procedure. A rule is **inactive unless its gene is present**, so adding it to a plan changes no existing
network, and rules never draw random numbers. See extending.

### Stages and schedules (experimental)
A **stage** is a registered operator `Tissue × Params × rng → Tissue`. A **schedule** is an ordered list of stages,
each optionally repeated or conditioned (`when`). Each repetition gets its own random stream, derived from the
development seed and the stage name, so inserting a stage does not change the randomness of the others.

### Params
Every stage parameter is a **Param**, which says where its value comes from:

| Param | Meaning | JSON |
|---|---|---|
| `Const` | set by hand | `{"type": "Const", "value": 0.5}` |
| `Gene` | read from the genome (evolvable) | `{"type": "Gene", "path": ["death", "p"], "lo": 0, "hi": 0.5}` |
| `Field` | read from a field at each cell's position | `{"type": "Field", "name": "gradient", "transform": "log"}` |
| `Fit` | fitted to data (supervised) | `{"type": "Fit", "target": "...", "init": 1, "lo": 0, "hi": 2}` |
| `Expr` | a formula of other values | `{"type": "Expr", "formula": "2 * x"}` |

### Tissue
The `Tissue` is the state that stages transform: positions (float64), parent and clone
ids (lineage), birth stage, generation, cycling flag, cell type, region, expression channels, sign, neuron parameters,
alive flag, and sparse connectivity (pre, post, weight, delay). `tissue.to_network()` compiles it into an `SNNNetwork`.

### Spiking network and neuron models
An `SNNNetwork` holds neurons (integer LIF parameters: leak shift, synaptic shift, threshold, refractory period;
or drivers: Poisson, burster) and synapses (weight, delay in ticks of 1 ms, up to 255). The same network runs with
`lif_int` (bit-identical across implementations), `two_compartment` or `hh`. See neuron models.

### Evolution
`evolve` runs one island: each generation is evaluated on **fresh development
seeds**, so selected genomes must work for every developmental outcome, not one lucky network. `choose` picks the
best on validation seeds, `audit` re-tests on held-out seeds (fitness = worst + 0.25 × mean − cost).

### Modules
A `Module` = plan + genome + inputs / outputs + validation + provenance + neuron model,
saved as JSON. `compose` assembles modules into a larger system (region names get a prefix, each module keeps its
genes, links add projections).

### Determinism
- Same inputs and seed → identical outputs (development and the integer kernel).
- Float models are deterministic per platform.
- Projects that depend on the toolbox should freeze **fingerprints** of the networks their results come from.


# Page: tutorials/index.md

## Tutorials

Each tutorial runs on a laptop CPU in a few minutes. Set `JAX_PLATFORMS=cpu` if you have no GPU.

| # | Tutorial | You will learn |
|---|---|---|
| 1 | cli | every command of `python -m neurogenesis` and of the bridge |
| 2 | first network | body plans, genomes, `grow`, stimulation and readout |
| 3 | neuron models cost | switching neuron models, f–I curves, cost estimates |
| 4 | genomes evolution | genes, [0, 1] coding, island evolution, audits |
| 5 | schedules | Projects, stages, Params, checkpoints (experimental) |
| 6 | fields geometry | geometries and fields (experimental) |
| 7 | rules plugins | writing your own developmental rule and stage |
| 8 | modules | saving, loading and composing modules |
| 9 | plasticity | STDP, Hebbian and three-factor rules |
| 10 | viewer | the browser viewer |
| 11 | desktop apps | the macOS, Windows and Linux apps |
| 12 | fitting validation | fitting `Fit` params to data, semi-supervised and Pareto evolution, validation assays, the module registry |
| 13 | connectome data | connectome manifests, downloads and streaming import |


> **PLACEHOLDER.** Jupyter notebook versions of every tutorial (rendered with myst-nb, runnable on Binder / Colab), and a video walkthrough of tutorials 1–4.


# Page: tutorials/cli.md

## Tutorial 1 — The command line

The toolbox has two command-line entry points:

| Entry point | Purpose |
|---|---|
| `python -m neurogenesis <command>` (also installed as `neurogenesis`) | generate, simulate, inspect networks; estimate costs |
| `python -m neurogenesis.bridge` | a JSON server on stdin / stdout used by the desktop apps and by other programs |

```bash
python -m neurogenesis --help
```

### `generate` — a network from a preset morphology
```bash
python -m neurogenesis generate --neurons 500 --morphology column --weight-scale 0.2 --refractory 3 --out column.json
```
```text
Generating SNN population (500 neurons, morphology: 'column')...
✓ Network successfully saved to /.../column.json
  Neurons: 500 | Synapses: 7,162
```

| Option | Default | Meaning |
|---|---|---|
| `--neurons` | 1000 | number of neurons |
| `--morphology` | `column` | `column`, `layered` or `microcircuit` |
| `--exc-ratio` | 0.80 | fraction of excitatory neurons |
| `--pace-ratio` | 0.05 | fraction of driver (pacemaker) neurons |
| `--radius`, `--height` | 30, 150 | column size (space units) |
| `--layers` | 4 | number of layers (`layered`) |
| `--conduction-velocity` | 4.0 | space units per tick; sets axonal delays |
| `--weight-scale`, `--inh-scale` | 1.0, 1.0 | scale all weights / inhibitory weights (`column`) |
| `--refractory` | 0 | absolute refractory period in ticks (`column`) |
| `--seed` | 42 | random seed |
| `--out` | `network.json` | output file |

These presets are fixed generators, useful for testing the kernels and viewers. Circuits grown from genomes are made
in Python (tutorial 2).

### `simulate` — run the integer kernel
```bash
python -m neurogenesis simulate --network column.json --ticks 1000 --raster raster.png
```
```text
Loading network from column.json...
Simulating 500 neurons for 1000 ticks (seed=12345)...
✓ Simulation Complete:
  Total spikes emitted: 11,554
  Mean population rate: 23.11 Hz
```

*[Figure: `raster.png` as written by the command.]*

### `stats` — topology of a network
```bash
python -m neurogenesis stats --network column.json
```
```text
--- SNN Topology & Biophysical Statistics: Cortical Column (L1-L6) ---
{
  "num_neurons": 500,
  "num_synapses": 7162,
  "density": 0.0287,
  "cell_types": {"excitatory_lif": 375, "inhibitory_lif": 100, "autonomous_burster": 13, "poisson_driver": 12},
  "in_degree": {"mean": 14.32, "std": 7.34, "min": 1, "max": 37},
  ...
}
```

### `export-presets` — example networks for the viewer
```bash
python -m neurogenesis export-presets --out-dir presets/
```
Writes `cortical_column_1000.json`, `microcircuit_100.json` and `layered_sheets_600.json`.

### `estimate` — memory and time before running
```bash
python -m neurogenesis estimate --neurons 3e8 --synapses-per-neuron 5000 --seconds 1
python -m neurogenesis estimate --neurons 1e5 --synapses-per-neuron 1000 --model lif_int --model hh
```
The first call per model and device runs three short benchmarks and caches the throughput in
`~/.cache/neurogenesis/throughput.json`. Every batched simulation calls the same estimator and refuses networks that
cannot fit (`CostError`), so you learn that a model is out of reach before trying it.

*[Figure: `neurogenesis.cost.estimate` for one simulated second on the documentation build machine (a 67 GB CPU node), for 10 and 1000 synapses per neuron. Memory is dominated by synapses, so the three models overlap; times are linear extrapolations of a short calibration.]*

### The bridge — the toolbox as a JSON server
```bash
python -m neurogenesis.bridge
```
The server prints a `ready` event, then answers one JSON request per line:

```bash
printf '%s\n' '{"id": 1, "method": "ping"}' \
              '{"id": 2, "method": "estimate", "params": {"neurons": 10000, "synapses": 1000000, "ticks": 1000}}' \
  | python -m neurogenesis.bridge 2>/dev/null
```
```text
{"event": "ready", "data": {"python": "3.11.15", "protocol": "1.0", "ngb": 1}}
{"id": 1, "result": "pong"}
{"id": 2, "result": {"estimates": [{"model": "lif_int", "neurons": 10000, "synapses": 1000000, "ticks": 1000,
  "memory_bytes": 25640000, "fits": true, "seconds_total": 15.48, "verdict": "fits (memory 25.6 MB of 66.8 GB available); ...
```

Any program that can start a process and read lines can drive development, simulation, evolution and modules this
way. See bridge and the full bridge protocol.

> **PLACEHOLDER.** Planned commands: `neurogenesis run project.json` (run a Project's schedule and save the network), `neurogenesis stages --json` and `neurogenesis ui-schema` (catalogue of stages and Params, task H3), `neurogenesis validate module.json` (re-run a module's assays, task E4), `neurogenesis data fetch` (connectome datasets, task I1). An animated terminal recording (asciinema) of this tutorial.


# Page: tutorials/first-network.md

## Tutorial 2 — Grow your first network

Goal: a sensor sheet that projects **topographically** onto a map, grown from a genome; then stimulate it and check,
in this simulation, that neighbouring sensors drive neighbouring map neurons.

### 1. The body plan
```python
import numpy as np
from neurogenesis.development import BodyPlan, FixedPlacement, Region, grow, region_of

side = 6
sheet = [[(i + 0.5) / side, (j + 0.5) / side, 0.5] for i in range(side) for j in range(side)]

plan = BodyPlan(
    name="topographic map",
    regions=[
        Region("sensor", origin=(0, 0, 0), size=(30, 30, 5), placement=FixedPlacement(sheet), role=1),  # role 1 = input
        Region("map", origin=(50, 0, 0), size=(30, 30, 10)),                                          # grown placement
    ],
    projections=[("sensor", "map")],
)
```
- A region is a box (`origin`, `size` in space units). Positions inside it are local coordinates in [0, 1]³.
- `FixedPlacement` puts cells at given local positions (supervised); without it, the genome decides how many cells
  there are and where they migrate.
- Distances in space units set conduction delays (`conduction_velocity`, default 20 units per tick).

### 2. The genome
```python
genome = dict(
    count=dict(map=200),                                              # number of map cells
    inh_weight_factor=2.0, w_clip=2047,
    migration=dict(map=dict(mean=np.full(3, 0.5), spread=0.35)),     # where map cells settle
    inhibitory=dict(map=dict(a0=-6.0, a=np.zeros(3))),                # logistic P(inhibitory | position): ~0 here
    guidance={("sensor", "map"): dict(
        B=np.diag([2.0, 2.0, 0.0]), c=np.zeros(3),                    # target position = B @ source position + c
        sigma=0.12, p0=0.9, w=900.0)},                                # tuning width, peak probability, weight
)
```
Axon guidance is chemoaffinity in its simplest form: each source cell aims at a target position, an affine function
of its own position, and connects to cells near that target with a Gaussian probability (width `sigma`).

### 3. Grow
```python
net = grow(plan, genome, seed=0)
print(net.num_neurons, net.num_synapses)
sensor, mp = region_of(net, plan, "sensor"), region_of(net, plan, "map")
```
`grow` is deterministic: the same `seed` gives the same network, bit for bit. Different seeds give different
individuals of the same "species" (genome).

*[Figure: The grown network (seed 0, top view): 36 sensor cells on a fixed sheet (left), 200 map cells placed by the genome (right), and the 124 sensor → map synapses coloured by the sensor's x position.]*

### 4. Stimulate and read out
```python
from neurogenesis.spiking.batch import simulate_counts

ext = np.zeros((2, net.num_neurons), np.int32)    # 2 stimuli × neurons: constant input current
ext[0, sensor[0]] = 120                            # stimulus 1: corner (0, 0) of the sheet
ext[1, sensor[-1]] = 120                           # stimulus 2: corner (1, 1)
counts = simulate_counts([net], [ext], ticks=50, blank=40)[0]   # spike counts per stimulus and neuron

u = np.asarray([(net.neurons[i].x - 50) / 30 for i in mp])      # map position along x, 0..1
for k in range(2):
    c = counts[k, mp]
    print(k, int((c > 0).sum()), (u * c).sum() / c.sum())
```
`simulate_counts` runs many networks and many stimuli in one batched JAX call. `blank` silent ticks precede every
stimulus so that responses do not carry over.

*[Figure: Topography of the grown projection (seed 0): for each of the 36 sensors, its position on the sheet against the mean position of its targets in the map (Pearson r = 0.97 on both axes in this network).]*

### 5. Save
```python
net.save_json("network.json")          # for the viewer
from neurogenesis.modules import Module
Module("topographic map", plan, genome, inputs=["sensor"], outputs=["map"],
       description="sensor sheet -> topographic map").save("module.json")
```

### Exercises
1. Increase `sigma` to 0.3: how does the precision of the map change?
2. Flip the sign of `B[0, 0]`: which side of the map now responds to stimulus 1?
3. Knock out the projection: `from neurogenesis.development import knockout`,
   `grow(plan, knockout(genome, projections=[("sensor", "map")]), seed=0)`.


# Page: tutorials/neuron-models-cost.md

## Tutorial 3 — Neuron models and cost

### Switching models on the same network
Every grown network runs with every registered model:
```python
from neurogenesis.spiking.batch import simulate_counts
for model in ("lif_int", "two_compartment", "hh"):
    counts = simulate_counts([net], [ext], ticks=300, model=model)
```
The models share synapses, axonal delays and driver neurons (Poisson drivers and bursters fire at the same ticks in
every model). They differ in how a neuron integrates its inputs between spikes.

| Model | Level | Integration |
|---|---|---|
| `lif_int` | integer point neuron | one update per tick; identical spikes in Python, JAX, the browser and C |
| `two_compartment` | soma + dendrite | Euler, 4 substeps per tick |
| `hh` | Hodgkin–Huxley (Na, K, leak) | exponential Euler, 40 substeps of 0.025 ms |

*[Figure: The grown topographic map (seed 0) simulated with the three models. Input: current 40 into one column of the sensor sheet for 40 ticks (grey bands), sweeping across the sheet, with 20 silent ticks between. Neurons are sorted by x, so the map response moves with the stimulus in all three models.]*

### Frequency–current curves
```python
import numpy as np
from neurogenesis.analysis import fi_curve
currents = np.arange(0, 40, 1)
for model in ("lif_int", "two_compartment", "hh"):
    p = fi_curve(currents, model=model)     # firing probability per tick; each current starts from rest
    rates_hz = 1000 * p                     # one tick = 1 ms
```

*[Figure: f–I curves of the three models with the default integer parameters (shift_v=5, shift_s=4, threshold=1024, refractory=3).]*

Units: 1 current unit = 0.74 µA/cm² in the Hodgkin–Huxley model, chosen so that its onset of repetitive firing falls
at the integer neuron's rheobase. This makes the models comparable on the same network, not quantitatively
equivalent. Details: neuron models.

### Cost: know before you try
```python
from neurogenesis.cost import estimate, check, CostError
e = estimate("hh", n_neurons=100_000, n_synapses=10**8, ticks=1000)
print(e["verdict"])
check("hh", 3 * 10**8, 3 * 10**8 * 5000, 1000)   # raises CostError: does not fit in memory
```
- Memory is counted array by array (neuron state, synapse arrays, delay buffers, records).
- Time comes from a throughput calibrated once per model and device (cached in
  `~/.cache/neurogenesis/throughput.json`).
- `simulate_counts` calls `check` first; pass `force=True` to override.

> **PLACEHOLDER.** A table of measured throughput on reference hardware (laptop CPU, workstation GPU, Apple silicon) and a "which model can I afford?" interactive calculator.


# Page: tutorials/genomes-evolution.md

## Tutorial 4 — Genes and evolution

### Genes are paths into the genome
```python
from neurogenesis.genome import get_gene, set_gene, guidance_genes, body_genes

set_gene(genome, ("guidance", ("sensor", "map"), "sigma"), 0.2)
set_gene(genome, ("guidance", ("sensor", "map"), "B", 0, 0), 1.5)     # paths reach into arrays
print(get_gene(genome, ("count", "map")))

genes = guidance_genes("sensor", "map") + body_genes("map")          # [(path, low, high), ...]
```
Gene groups collect the genes of one mechanism: `body_genes`, `guidance_genes`, `field_genes`,
`differentiation_genes`, `identity_genes`, `typed_guidance_genes`. You can also write the list by hand.

### [0, 1] coding and JSON
```python
from neurogenesis.genome import to_unit, from_unit, to_json, from_json
u = to_unit(genome, genes)            # numpy vector in [0, 1]
g2 = from_unit(genome, genes, u)      # a new genome (the original is not modified); counts stay integers
d = to_json(g2)                       # JSON-ready: projection keys become "sensor->map"
```

### Evolving one island
`evaluate(genomes, seeds)` returns one fitness per genome and an info dict per genome. **Every generation gets fresh
development seeds**, so the fitness of a genome is about all the networks it can grow, not one lucky individual.

```python
import numpy as np
from neurogenesis.development import grow
from neurogenesis.spiking.batch import simulate_counts
from neurogenesis.evolution import evolve, choose, audit

def evaluate(genomes, seeds):
    fits, infos = [], []
    for g in genomes:
        per_seed = []
        for s in seeds:
            net = grow(plan, g, seed=s)
            counts = simulate_counts([net], [make_stimuli(net)], ticks=50, blank=40)[0]
            per_seed.append(score(counts))          # your task metric
        per_seed = np.asarray(per_seed)
        fits.append(per_seed.min() + 0.25 * per_seed.mean())   # robust: the worst seed matters most
        infos.append(dict(per_seed=per_seed.tolist()))
    return fits, infos

P, history = evolve(genome, genes, evaluate, island=0, pop=32, gens=40, seeds_per_gen=4)
best, info = choose(genome, genes, P, evaluate, seeds=(2000, 2001, 2002, 2003))   # validation seeds
```
Run several islands (`island=0, 1, 2, ...`) in parallel processes and choose among their final populations. Then
**audit** the chosen genome on held-out seeds never used during the search (`neurogenesis.evolution.audit`), including
knockouts that should break the function.

*[Figure: A small run of `evolve` on the topographic-map example: one island of 12 genomes, 3 fresh development seeds per generation, 4 guidance genes (B[0,0], B[1,1], B[0,1], sigma), toy score = topographic correlation of the projection. Best and median fitness (worst + 0.25 × mean over seeds) per generation.]*

### Before you evolve: a capacity test
Evolution can only find what the architecture can do. First set the genome **by hand** (or with an oracle wiring)
and check that the task is solvable. If it is not, no search will find it, and the time is better spent on the plan.

> **PLACEHOLDER.** Tutorial on multi-island runs on a cluster (SLURM array jobs), checkpointing and resuming evolution, and a gallery of evolved modules with their audit reports.


# Page: tutorials/schedules.md

## Tutorial 5 — Projects, schedules and stages (experimental)

> **Experimental.** The stage library (roadmap phase C) is new and its API may still change. Every stage is a no-op with empty parameters, stages that write delays clip them to `max_delay` (default: the storage bound of 255 ticks) with a warning, and `grow_axons` raises an error when a projection has no guidance. Every stage's parameters and known limits are listed in stages.

### A schedule is data
A schedule is an ordered list of stages. Each entry names an **operator** (a registered stage), its **params**
(each a Param, see concepts), and optionally `repeat` and `when`.

```python
from neurogenesis.stages.base import run_schedule

project = {
    "version": "1.0.0", "fields": {}, "plugins": [],
    "schedule": [
        {"name": "seed",   "operator": "seed_cells",  "params": {"N": 20, "jitter": 5.0}},
        {"name": "divide", "operator": "proliferate", "params": {"prob": 0.5, "jitter": 1.0}, "repeat": 3},
        {"name": "death",  "operator": "apoptosis",
         "params": {"prob": {"type": "Gene", "path": ["death", "p"], "lo": 0.0, "hi": 0.5}}},
    ],
}
genome = {"death": {"p": 0.2}}

checkpoints = run_schedule(project, genome, seed=0, checkpoints=True)
print([int(t.alive.sum()) for t in checkpoints])
```
```text
[20, 29, 43, 65, 56]
```
- `seed_cells` places 20 cells; `proliferate` runs three times (each cell divides with probability 0.5);
  `apoptosis` removes cells with a probability read from the genome — an evolvable gene.
- `checkpoints=True` returns the tissue after every stage repetition, for previews and analysis.
- Each repetition has its own random stream derived from `(seed, stage name, repetition)`: inserting a stage does not
  change what the others draw.

*[Figure: One frame per checkpoint of a schedule: `seed_cells` (12 founders in a sphere) → `proliferate` ×5 (p = 0.6) → `apoptosis` (p = 0.15) → `apoptosis` by crowding → `differentiate` (every third clone inhibitory, triangles) → `migrate` ×3 (radial). Colour = clone. Generated by `docs/_scripts/make_media.py`.]*

### A complete pipeline from JSON
Two regions seeded in boxes, divisions, a gene-regulatory step driven by a gradient, fates from the gradient,
axons whose tuning width is a gene, myelination, activity-dependent refinement and pruning:

```python
import json
import numpy as np
from neurogenesis.stages.base import run_schedule
from neurogenesis.spiking.batch import simulate_counts

gene = lambda path, lo, hi: {"type": "Gene", "path": path, "lo": lo, "hi": hi}
project = {
    "version": "1.0.0", "plugins": [],
    "fields": {"tissue": {"type": "Box", "center": [0, 0, 0], "size": [40, 40, 10]},
               "target": {"type": "Box", "center": [60, 0, 0], "size": [40, 40, 10]},
               "grad": {"type": "AnalyticField", "expr": "x / 40 + 0.5"}},
    "schedule": [
        {"name": "seed", "operator": "seed_cells", "params": {"N": 30, "geometry": "tissue", "region": 0}},
        {"name": "seed2", "operator": "seed_cells", "params": {"N": 30, "geometry": "target", "region": 1}},
        {"name": "divide", "operator": "proliferate", "params": {"prob": 0.5, "jitter": 1.0}, "repeat": 2},
        {"name": "express", "operator": "express",
         "params": {"K": 1, "morphogens": [{"field": {"type": "Field", "name": "grad"}, "weights": [1.0]}]}},
        {"name": "fate", "operator": "differentiate", "params": {"types": [
            {"type_id": 1, "condition": "field_grad < 0.5", "sign": 1},
            {"type_id": 2, "condition": "field_grad >= 0.5", "sign": -1}]}},
        {"name": "axons", "operator": "grow_axons", "params": {
            "projections": [[0, 1]],
            "guidance": {"0->1": {"B": [[1, 0, 0], [0, 1, 0], [0, 0, 0]], "c": [0, 0, 0],
                                  "sigma": gene(["g", "sigma"], 0.05, 0.5), "p0": 0.8, "w": 300}},
            "conduction_velocity": 2.0}},
        {"name": "myelin", "operator": "myelinate", "params": {"factor": 0.5}},
        {"name": "refine", "operator": "refine_by_activity", "params": {"ticks": 100}},
        {"name": "prune", "operator": "prune", "params": {"threshold": 1}},
    ]}
project = json.loads(json.dumps(project))          # a project is plain JSON
tissue = run_schedule(project, genome={"g": {"sigma": 0.2}}, seed=0)
net = tissue.to_network()
ext = np.zeros((1, net.num_neurons), np.int32); ext[0, :5] = 100
for model in ("lif_int", "two_compartment", "hh"):
    print(model, int(simulate_counts([net], [ext], ticks=100, model=model)[0].sum()))
```
```text
lif_int 203
two_compartment 205
hh 27
```
The network has 131 cells and 319 synapses (166 inhibitory, from the type-2 cells), with delays of 10–22 ticks
after myelination. In this run `refine_by_activity` has no `rule`, so it leaves the weights unchanged.

### Genes of a schedule
```python
from neurogenesis.params import collect_genes
print(collect_genes(project["schedule"]))      # [(path, low, high, integer), ...] — ready for evolution
```

### Cost of a schedule
```python
from neurogenesis.stages.base import schedule_cost
print(schedule_cost(project, N=10_000, E=1_000_000))    # (bytes, operations), declared by each stage
```

### Available stages

| Operator | Task | What it does |
|---|---|---|
| `seed_cells` | C1 | create cells at a point, in a geometry, with a region and type |
| `proliferate` | C2 | cell division with a probability; daughters inherit lineage |
| `apoptosis` | C3 | cell death: by probability, density, or missing inputs / outputs |
| `migrate` | C4 | move cells (direction, radial, towards a target; stop field, boundary) |
| `sort` | C5 | reorder cells along an axis by a key |
| `express` | C6 | gene-regulatory step on expression channels (morphogens, neighbours) |
| `differentiate` | C7 | assign cell types and neuron parameters from conditions |
| `grow_axons` | C8 | connections by axon guidance |
| `place_by_hand`, `connect_by_hand` | C9 | explicit cell positions and synapses (supervised) |
| `prune`, `scale`, `myelinate` | C10, C12 | remove weak synapses, scale weights, shorten delays |
| `refine_by_activity` | C11 | simulate inside development and apply a plasticity rule |
| `legacy_grow_stage` | A5 | the original `grow` procedure as one stage |

Every stage's parameters, defaults and limits: stages (also through the bridge `stages` method).

### Legacy plans as schedules
Any `BodyPlan` can be compiled into a Project whose schedule reproduces `grow()` bit for bit:
```python
from neurogenesis.development.legacy_compiler import compile_plan_to_project
project = compile_plan_to_project(plan)
tissue = run_schedule(project, genome, seed=0, legacy_stream=True)
net = tissue.to_network()
```
`legacy_stream=True` exists only for legacy plans (one random stream shared by all stages, as in `grow`); new
schedules never use it.

> **PLACEHOLDER.** A full worked example — a layered sheet grown from progenitors (seed → proliferate → migrate into layers → differentiate by layer → grow axons → refine by spontaneous waves → prune) — once the fix wave is merged (roadmap milestone M2, example G2.3), with a GIF of every stage.


# Page: tutorials/fields-geometry.md

## Tutorial 6 — Geometry and fields (experimental)

> **Experimental.** Geometries and fields (roadmap phase B) are new and their API may still change. Geometries and fields declared in a Project's `fields` are built when the schedule runs, and unknown types raise an error.

### Geometries
Shapes in 2D or 3D, used to seed cells, constrain migration and define regions.

```python
import numpy as np
from neurogenesis.geometry import Box, Sphere, Union, Difference, geometry_from_dict

shell = Difference(Box([0, 0, 0], [2, 2, 2]), Sphere([0, 0, 0], 0.5))   # a cube with a spherical hole
pts = shell.sample(1000, np.random.default_rng(0))                      # 1000 points inside, shape (1000, 3)
shell.contains(np.zeros((1, 3)))                                        # array([False])
d = shell.to_dict()          # {'type': 'Difference', 'a': {'type': 'Box', ...}, 'b': {'type': 'Sphere', ...}}
same = geometry_from_dict(d)                                            # JSON round trip
```

| Type | Parameters |
|---|---|
| `Box` | `center`, `size` |
| `Sphere` | `center`, `radius` |
| `Ellipsoid`, `Cylinder` | see api |
| `Sheet2D`, `CurvedSheet` | 2D sheets, flat or curved, for retinas, cortical sheets, skin |
| `Union`, `Difference` | constructive solid geometry |
| `mesh`, `voxel_mask` | triangle meshes and voxel masks (roadmap B2) |

Every geometry gives `contains(points)`, `sample(n, rng)`, `bounding_box()`, `local_coordinates(points)` and
`to_dict()`.

### Fields
Fields are values on space: morphogen gradients, layers, maps. A field is sampled at cell positions,
`field.sample(points)`, and can drive any stage parameter through a `Field` Param.

```python
from neurogenesis.fields.core import AnalyticField, LayerStackField

depth = AnalyticField("z")                                   # a linear gradient along z
layers = LayerStackField(depth, thicknesses=[0.2, 0.3, 0.5]) # three layers by depth
lw = layers.sample(pts)            # shape (n, 2): layer label, relative depth inside the layer (0..1)
label, rel_depth = lw[:, 0], lw[:, 1]
```

Operations (`Add`, `Threshold`, `GaussianSmooth`, `DistanceTransform`) build new fields from others; `maps`
provides orientation-like maps from pinwheel centres.

> **PLACEHOLDER.** Figures: a gallery of geometries (sampled points), a layer stack painted on a curved sheet, and a pinwheel map with its charges; a GIF of cells migrating along a gradient until a stop field. Tutorial for painting fields in the desktop apps' field painter.


# Page: tutorials/rules-plugins.md

## Tutorial 7 — Your own rules, stages and models

The toolbox is extended through registries. Your code lives in **your** package (a plugin); projects and modules
list the plugin, and it is imported whenever they are loaded.

### A developmental rule
Rules modify the fixed growth procedure in five phases: `placement`, `shift`, `target`, `probability`, `weight`.

```python
# my_project/rules.py
import numpy as np
from neurogenesis.development import register

@register("weight", "saturating_receptor")
def saturating_receptor(ctx):
    """gd["w_max"]: receptors saturate; weights above w_max are compressed."""
    if "w_max" in ctx.gd:                      # inactive unless its gene is present
        m = ctx.gd["w_max"]
        ctx.w = np.sign(ctx.w) * m * np.tanh(np.abs(ctx.w) / m)
```
```python
plan = BodyPlan(..., plugins=["my_project.rules"])
plan.rules["weight"] += ("saturating_receptor",)
genome["guidance"][("a", "b")]["w_max"] = 300.0
```
Rules must (1) act only when their genes are present, (2) draw no random numbers, (3) be listed in the plan. The
built-in rules are listed in extending.

### A developmental stage
```python
# my_project/stages.py
import numpy as np
from neurogenesis.stages.base import register_stage
from neurogenesis.params import Const, resolve

@register_stage("shrink")
class Shrink:
    """Scales positions towards the origin by `factor` (1 = no change)."""

    def run(self, tissue, params, genome, fields, rng):
        f = resolve(params.get("factor", Const(1.0)), genome, fields, tissue.positions)
        tissue.positions = tissue.positions * f
        return tissue

    @staticmethod
    def cost(N, E, params):
        return 3 * 8 * N, 3 * N          # bytes, operations
```
A stage must: read every parameter through a Param, use only the `rng` it is given, be a no-op with neutral
parameters, declare its cost, and come with a test on a toy tissue (see `AGENTS.md`).

### A neuron model
Register a module with `name`, `description`, `substeps`, `state_bytes`, `flops`, `init_state(net, seed)` and
`tick(net, state, ext)`, using `models.base.drivers` and `models.base.transmit` so that drivers and delays behave as
in every other model:
```python
from neurogenesis.spiking.models import register_model
import my_project.izhikevich as izh
register_model(izh)
```
Details: neuron models.

### A geometry or field type
```python
from neurogenesis.geometry import register_geometry
from neurogenesis.geometry.primitives import Geometry

@register_geometry("torus")
class Torus(Geometry):
    ...   # contains, sample, bounding_box, local_coordinates, to_dict, from_dict
```

> **PLACEHOLDER.** A cookiecutter template for a plugin / module repository (package layout, tests, CI, docs) and a community index of published plugins.


# Page: tutorials/modules.md

## Tutorial 8 — Modules: share and compose circuits

A **module** is what one person designs or evolves and another reuses as-is: a body plan, a genome, an interface
(input and output regions), validation records, provenance and the neuron model it was validated with.

### Save
```python
from neurogenesis.modules import Module

m = Module("topographic map", plan, genome,
           inputs=["sensor"], outputs=["map"],
           description="sensor sheet -> topographic map; input: constant current 120 on sensor cells",
           validation={"seeds": [0, 1, 2], "metric": "topographic precision", "pass_fraction": 1.0},
           provenance={"authors": ["..."], "repository": "...", "commit": "..."},
           neuron_model="lif_int")
m.save("topographic_map.module.json")
```

### Load and grow
```python
m = Module.load("topographic_map.module.json")   # its plugins are imported, its genes kept
net = m.grow(seed=3)
```

### Compose
```python
from neurogenesis.modules import compose
eye = Module.load("eye.module.json")
circuit = Module.load("circuit.module.json")
system = compose("eye+circuit", {"eye": eye, "circuit": circuit},
                 links=[("eye.output", "circuit.input",
                         dict(B=np.eye(3), c=np.zeros(3), sigma=0.2, p0=0.5, w=200.0))])
net = system.grow(seed=0)
```
- Region names get the module prefix (`eye.output`); each module keeps its own genes and plugin rules.
- Links add projections with their own guidance genes, which can themselves be evolved.
- **Re-audit the composed system**: a module validated alone may behave differently once connected.

### Validation records
Write in `validation` how the module was tested: stimuli, readout, metric, the development seeds used, and the
fraction of held-out seeds that pass. A declarative assay format, re-run by `neurogenesis validate module.json`, is
planned (roadmap task E4).

> **PLACEHOLDER.** A module registry (roadmap G1): `neurogenesis install <module>`, semantic versions, compatibility with toolbox versions, and a browsable gallery of published modules with their validation reports and screenshots.

### Versions, registry and validation
`Module` has a semantic `version` and a `toolbox` range. `python -m neurogenesis install module.json` copies it into
the local catalogue, and `neurogenesis.modules.registry.get(name)` loads the latest compatible version. Assays stored
in `validation["assays"]` are rerun by `python -m neurogenesis validate module.json`. See fitting validation.


# Page: tutorials/plasticity.md

## Tutorial 9 — Synaptic plasticity

`neurogenesis.spiking.plasticity` provides three rules, each usable **offline** (`apply` on a recorded spike train)
or **online**, tick by tick (`step`, no T × N memory):

| Rule | Class | Parameters |
|---|---|---|
| Pair-based STDP | `STDPRule` | `A_plus`, `A_minus`, `tau_plus`, `tau_minus`, `w_min`, `w_max`, `soft_bounds`, `integer_traces` |
| Hebbian with normalisation | `HebbianRule` | `eta`, `norm_target`, `norm_type` |
| Three-factor (eligibility trace × modulator) | `ThreeFactorRule` | `eta`, `tau_e` |

### Offline
```python
import jax.numpy as jnp
from neurogenesis.spiking.plasticity import STDPRule

rule = STDPRule(A_plus=1.0, A_minus=1.05, tau_plus=20, tau_minus=20, w_min=0, w_max=1000)
new_w = rule.apply(weights, pre, post, delays, spikes, original_weights=weights)   # spikes: (T, N) array
```

### Online
```python
tr_pre, tr_post = rule.init_state(num_neurons, num_synapses)
for t in range(T):
    weights, tr_pre, tr_post = rule.step(weights, pre, post, spikes[t], arrivals[t], tr_pre, tr_post)
```

### Inside development
The `refine_by_activity` stage (C11) runs a simulation inside a schedule and applies a rule, so that spontaneous
activity can shape connectivity before the network is used (see schedules).

`refine_by_activity` does nothing unless a `rule` is given, and keeps every synapse's sign (Dale's principle):
excitatory and inhibitory weights have separate bounds.

Each rule declares its cost (`declared_cost(num_neurons, num_synapses, ticks)`).

> **PLACEHOLDER.** Figures: the STDP window measured from the implementation; weight distributions before and after refinement by spontaneous waves; a GIF of receptive fields sharpening during activity-dependent refinement.


# Page: tutorials/viewer.md

## Tutorial 10 — The browser viewer

The viewer (`viz/`, TypeScript + three.js) shows networks in 3D and simulates them live in the browser with the same
integer kernel as Python (identical spikes).

```bash
cd viz
npm ci
npm run build:kernel
npm run dev          # http://localhost:5173
```

1. **Import** a network JSON (from `net.save_json(...)`, the CLI `generate` command, or `export-presets`).
2. Orbit, pan and zoom; click a neuron to inspect and edit its parameters.
3. Colour neurons by region, role, sign, mode or compartment.
4. Run the simulation; watch spikes in 3D and in the raster of selected populations.

*[Figure: The grown topographic map of tutorial 2 (seed 0, 236 neurons, 124 synapses) imported into the viewer: sensor sheet (left) and map (right).]*

*[Figure: The CLI column (`generate --neurons 500 --weight-scale 0.2 --refractory 3`) simulated live in the browser; white neurons are spiking, and the population raster scrolls at the bottom right.]*

> **PLACEHOLDER.** A GIF of the viewer simulating a network while the camera orbits (`docs/_scripts/viewer_capture.py` writes it, but headless software-WebGL capture was too heavy for the documentation build machine; capture it on a desktop with a GPU).

The viewer is the reference implementation of the browser kernel and the lightweight web demo. Full editors
(projects, geometry, fields, schedules) live in the desktop apps (desktop apps).

> **PLACEHOLDER.** A hosted web demo on GitHub Pages (the built viewer with preset networks), embedded in this page.


# Page: tutorials/desktop-apps.md

## Tutorial 11 — Desktop apps

Native apps for macOS (SwiftUI + Metal) and for Windows and Linux (Avalonia, one C# code base). They need no Python
installation: each bundles a Python runtime with the toolbox.

| Layer | Shared by every app |
|---|---|
| Python bridge | `python -m neurogenesis.bridge`: development, evolution, modules, projects (bridge protocol) |
| C kernel `nglif` | live simulation with spikes identical to Python, JAX and the browser, checked against golden fixtures |
| NGB1 | binary network files, int32 indices, optional extension columns |
| UI schema | panels for stages, Params, models and genes are generated from the toolbox, not hand-written (ui schema) |
| Visual spec | one colour and style specification for every app |

Because the apps learn stages, Params and models from the toolbox at run time, a stage merged in Python appears in
every app without changing the apps.

### macOS
```bash
cd desktop/mac && ./build.sh        # developer build; a signed .dmg is produced by the release workflow
```

### Windows and Linux
See `desktop/avalonia/README.md` (in progress, roadmap task H5).

> **PLACEHOLDER.** Screenshots of each app (network view, inspector, live development preview, schedule editor generated from the UI schema, evolution progress), a GIF of growing and simulating a project end to end, and installation instructions for the released packages.


# Page: tutorials/fitting-validation.md

## Tutorial 12 — Fit to data, validate, share

This tutorial covers three tools that work on top of a Project or a module:
- **fitting** `Fit` params to measured targets (`neurogenesis.fitting`, task E2);
- **semi-supervised evolution**: genes evolved while `Fit` params are refitted, and Pareto fronts
  (`neurogenesis.evolution.semi_supervised`, E3);
- **validation assays** stored in modules, and the **module registry** (`neurogenesis.validation`,
  `neurogenesis.modules.registry`, E4 and G1).

### Fit params to targets
A `Fit(target, init, lo, hi)` param is a free number named `target`. Until it is fitted it resolves to `init`. Fitted
values are stored in the genome under `"__fit__"`, so the same genome always develops the same tissue.

```python
from neurogenesis.fitting import fit

project["schedule"][1]["params"]["prob"] = {"target": "p_div", "init": 0.1, "lo": 0.0, "hi": 1.0}
targets = [{"metric": "cell_count", "value": 300, "scale": 10},
           {"metric": "layer_thickness", "cell_type": 1, "axis": 2, "value": 0.2, "scale": 0.02}]
r = fit(project, targets, seeds=(0, 1, 2), iterations=30)
r.values          # {"p_div": 0.73...}
r.genome          # the base genome plus {"__fit__": {"p_div": 0.73...}}
```

Each target is `{"metric", "value", "scale" (tolerance), "weight", ...metric options}`. The loss is the mean over the
development seeds of Σ weight · ((metric − value) / scale)². Several seeds per evaluation make the fit robust to
developmental noise. The optimiser is a CMA-ES on the [0, 1]-coded values (gradient free, deterministic for a given
`seed`).

| Metric | Options | Value |
|---|---|---|
| `cell_count` | `region`, `cell_type` | number of living cells |
| `density` | `volume` (required), selectors | cells per unit volume |
| `layer_thickness` | `axis` (0, 1, 2 or a vector), `lower`, `upper` percentiles (5, 95), selectors | extent of the cells along the axis |
| `synapse_count`, `mean_in_degree` | `pre_region`, `pre_cell_type`, `post_region`, `post_cell_type` | synapses; synapses per post cell |
| `connection_probability` | pre / post selectors, `d_min`, `d_max` | fraction of pairs at that distance that are connected |
| `firing_rate` | selectors, `ticks` (500), `ext_current` (0), `sim_seed` | mean rate (Hz) in a `lif_int` simulation |

Add your own with `@register_metric("name")` on a function `(tissue, options) -> float`.

**What the data can constrain.** Before searching, `fit` measures the Jacobian of the metrics with respect to the Fit
values (central differences over ±5 % of each range, on the fitting seeds; `neurogenesis.fitting.jacobian`) and warns:
- about a Fit param that moves no target metric. It cannot be fitted from these targets: the model does not use it, a clip
  masks it, or no target sees it. Drop it or add a target it affects;
- about a target that no Fit param moves. It cannot be matched whatever the fitted values: drop it, give it weight 0 (a
  diagnostic) or add a Fit param that acts on it.

`FitResult.jacobian` and `FitResult.identifiability` (`identifiability_report`: the params and targets above, the singular
values of the Jacobian scaled by each target's tolerance and weight, and its rank) keep the result. The check costs
2 × params × seeds developments; `fit(..., check_identifiability=False)` skips it. It is a local check at the starting
point: a param can matter elsewhere in its range, and correlated params are not detected (a rank below the number of
params says that some combination is unconstrained).

From the command line:
```
python -m neurogenesis fit --project project.json --targets targets.json --seeds 0,1,2 --out fitted_genome.json
```

### Evolve genes while refitting
`inner_fit_evaluator` wraps any evaluator of `islands.evolve`: each genome is first fitted to the data targets, then
scored. With `refit=False` the `Fit` values are kept as they are ("fixed").

```python
from neurogenesis.evolution.islands import evolve
from neurogenesis.evolution.semi_supervised import inner_fit_evaluator

ev = inner_fit_evaluator(project, targets, my_evaluate, fit_seeds=(0,), iterations=5)
P, hist = evolve(base_genome, genes, ev, pop=16, gens=20)
hist[-1]["best_info"]["fit"]          # fitted values of the best genome
```

For several objectives, `evolve_pareto(base, genes, evaluate)` takes an evaluator returning an array
(population × objectives, all maximised) and keeps the population on the Pareto front (non-dominated sorting and
crowding distance, as NSGA-II). `pareto_front(F)` returns the non-dominated rows.

### Validation assays
An assay is a JSON dict stored in `module.validation["assays"]`:
```json
{"name": "responds", "ticks": 100, "blank": 20, "seeds": [1000, 1001, 1002],
 "stimuli": [{}, {"sensor": 300}],
 "readout": {"region": "map", "measure": "rate"},
 "metric": "difference", "gate": {"min": 20.0}, "pass_fraction": 1.0}
```
Each seed grows one individual, runs the stimuli in sequence and computes the metric (`mean`, `difference`
(last − first), `ratio`, `selectivity`, `stimulus:<k>`) of the readout (`rate` in Hz or `fraction_active`). Add
`"knockout": {"regions": [...], "projections": [[src, dst]]}` for a knockout assay.

```
python -m neurogenesis validate module.json            # exit status 1 if an assay fails
```

### Module registry
Modules carry a semantic `version` and the toolbox versions they work with (`toolbox`, e.g. `">=0.1, <0.2"`).
```
python -m neurogenesis install module.json             # refused if incompatible with this toolbox
python -m neurogenesis modules                         # list the catalogue
```
```python
from neurogenesis.modules.registry import get
m = get("topographic map")              # latest version compatible with this toolbox
m = get("topographic map", "1.2.0")     # a given version (checksum verified)
```
The catalogue lives in `$NEUROGENESIS_MODULES` (default `~/.cache/neurogenesis/modules`).


# Page: tutorials/connectome-data.md

## Tutorial 13 — Import a connectome

The data layer (`neurogenesis.data`, Phase I) reads public connectome datasets described by a **manifest**, without
naming any dataset in the code.

### Describe a dataset
A manifest `datasets/<id>.json` lists the files (URL, SHA-256, size), the license and citation, the column names of
the neuron and edge tables, and the units:
```json
{"id": "my_dataset", "license": "CC-BY-4.0", "citation": "...", "url": "https://example.org/data/",
 "files": [{"name": "neurons.csv", "sha256": "...", "size": 123456},
           {"name": "edges.csv", "sha256": "...", "size": 7890123}],
 "tables": {"neurons": {"file": "neurons.csv", "format": "csv",
                        "columns": {"id": "root_id", "x": "pos_x", "y": "pos_y", "z": "pos_z",
                                    "cell_type": "type", "region": "region", "side": "side", "transmitter": "nt"}},
            "edges": {"file": "edges.csv", "format": "csv",
                      "columns": {"pre": "pre_root_id", "post": "post_root_id", "count": "syn_count"}}},
 "units": {"voxel_size": [0.004, 0.004, 0.04], "origin": [0, 0, 0], "mirror": [], "unit": "um"}}
```

### Fetch
```
python -m neurogenesis data info my_dataset              # size, license, citation
python -m neurogenesis data fetch my_dataset             # asks before downloading; --yes to skip the question
```
Files go to `$NEUROGENESIS_DATA` (default `~/.cache/neurogenesis/data/<id>/`), never into the repository. An
interrupted download is resumed; every file is checked against its SHA-256.

### Import a sub-circuit
```python
from neurogenesis.data import import_connectome

tissue, info = import_connectome(
    "my_dataset",
    filters={"cell_type": ["T1", "re:^T2"], "side": ["left"], "min_synapses": 5,
             "bbox": [[0, 0, 0], [200, 200, 100]]},                     # bbox in space units, after registration
    weight_map={"kind": "hill", "w_max": 800, "k": 20, "n": 1},
    transmitter_signs={"gaba": -1, "glutamate": 1},
    conduction_velocity=1.0)                                             # space units per tick
info["license"], info["citation"], info["source_ids"], info["type_names"]
```
- Tables are streamed in batches: CSV with NumPy (no quoted commas inside fields), or Parquet / Feather / CSV with
  pyarrow (`pip install -e ".[data]"`). Only edges between selected neurons are kept, summed over neuropils.
- Positions are registered: raw × `voxel_size` + `origin`, then `mirror` axes negated. Delays are soma distance /
  velocity, clipped to 31 ticks with a counted warning.
- Weights: `linear` (gain × count), `hill` (w_max cⁿ / (kⁿ + cⁿ)) or `log` (gain log(1 + c)), clipped to
  `w_clip` (512–2047); the sign comes from the presynaptic transmitter (`transmitter_signs`). With a pharmacology
  `profile` the transmitter bits of the cells are set too, so `assign_receptors` and `pharmacology` stages can follow.

The result is an ordinary `Tissue`: freeze it (`neurogenesis.stages.freeze.freeze`) to simulate it, or use it as a
target for fitting (`neurogenesis.fitting`).


# Page: use-cases/index.md

## Use cases

| Use case | Workflow | Status |
|---|---|---|
| topographic map | unsupervised (genes) | runs today (`examples/topographic_map.py`) |
| shared modules | supervised + genetic, shared as modules | API available; example modules planned |
| layered sheet | semi-supervised (hand-made layers + stages) | planned (example G2.3) |
| connectome constrained | supervised import + semi-supervised fitting to connectome statistics | planned (phases I, P, N) |
| developmental hypotheses | knockouts, timing changes, activity manipulations | partially available |
| retinothalamic | genes + activity, four conditions with pre-registered hypotheses | runs today (`examples/retinothalamic.py`) |
| teaching | interactive exploration in the viewer and apps | available |


# Page: use-cases/topographic-map.md

## Topographic projection grown from genes

**Question.** Can a projection that preserves neighbourhoods (a topographic map) be specified by a handful of
guidance genes, and does it hold for every developmental seed?

**Model.** A fixed 6 × 6 sensor sheet projects onto a map of 200 cells whose number and positions come from the
genome. Each sensor axon aims at an affine function of its own position (positional chemoaffinity; Sperry 1963) and
connects to map cells near that target with a Gaussian probability.

*[Figure: The circuit appearing step by step (see the frame titles for what each frame adds).]*

*[Figure: Sensor position against the mean position of its targets in the map, for the grown network.]*

**Run it.**
```bash
python examples/topographic_map.py --cpu
```
```text
stimulus at corner (0, 0): 4 map neurons fire, spike-weighted mean u of the map 0.09
stimulus at corner (1, 1): 7 map neurons fire, spike-weighted mean u of the map 0.74
```
In this simulation, opposite corners of the sheet drive opposite sides of the map.

**Going further.**
- Make `sigma`, `B` and `c` genes (`guidance_genes("sensor", "map")`) and evolve them for topographic precision
  across development seeds (genomes evolution).
- Add the `topographic` target rule or a refinement stage with correlated activity (retinal-wave-like) to sharpen
  the map (Willshaw & von der Malsburg 1976).

> **PLACEHOLDER.** A quantitative study: precision vs sigma and number of map cells, across 100 development seeds, with and without activity-dependent refinement.


# Page: use-cases/shared-modules.md

## Building circuits from shared modules

**Idea.** One group designs or evolves a circuit — for example an early sensory stage whose output cells respond to
increments and decrements of their input (ON / OFF) — validates it, and publishes it as a module. Another group
loads it and connects it to its own circuit, without re-evolving it.

**How.**
1. Plan and assay first (what are the inputs, the outputs, the metric).
2. **Capacity test** with a hand-set genome: can this architecture do the task at all?
3. Evolve from a founding genome that does not contain the hand-set answer.
4. Audit on development seeds never used, including knockouts that biology says must break the function.
5. Save with `Module(..., validation=..., provenance=...)`; publish the JSON and the plugin package.
6. Others `Module.load(...)` and `compose(...)` it (modules), then re-audit the composed system.

Recommended repository layout for a module: see extending.

> **PLACEHOLDER.** A worked example module (an ON / OFF sensory stage) with its capacity test, evolution logs, audit report and screenshots, published in its own repository and composed with the topographic-map example.


# Page: use-cases/layered-sheet.md

## A layered sheet from progenitors (semi-supervised)

**Idea.** Draw what is known by hand — the sheet, its layer boundaries — and let development do the rest:
progenitors advance through cell cycles and exit (`cell_cycle_clock`), progressively restrict their laminar competence
(`temporal_competence`), differentiate from their competence at cycle exit (`differentiate`), migrate to the target
layer of their cell type (`migrate`), grow axons with laminar targeting (`grow_axons`), and refine connections with
spontaneous activity (`refine_by_activity` → `prune`). Genes control the rates, the timing and the guidance; the
scaffold constrains them.

**Stages.** `seed_cells` (in a `Sheet2D` or `CurvedSheet`) → `cell_cycle_clock` (C14) → `temporal_competence` (C15) →
`proliferate` (C2, repeated across 3 cycles) → `differentiate` (C7, cell types from competence at cycle exit) →
`migrate` (C4, stop at the target layer of each cell's type in a `LayerStackField`) → `grow_axons` (C8, laminar
targeting) → `refine_by_activity` (C11, STDP) → `prune` (C10).

**Run the worked example.**
```bash
python examples/layered_sheet.py --cpu --out example_out
```
```text
Layered sheet grown: 112 cells, 686 synapses (587 exc, 99 inh).
Cells per type (0: deep exc, 1: mid exc, 2: superficial exc, 3: superficial inh): [22, 22, 51, 17]
Birth cycle vs laminar depth rank correlation: 0.872 (p=3.41e-78)
Connection matrix (pre \ post):
  [48, 30, 4, 1]
  [21, 47, 35, 9]
  [5, 45, 274, 68]
  [1, 11, 69, 18]
Delay distribution: min=1, max=20, mean=6.79
```
This produces `project.json` (Project schema), `network.json` (for the 3D viewer), and `stats.json`.

### Quantitative comparison of birth-order rules (in this simulation)

| Rule | Target layer formula (`migrate`) | Spearman rank correlation $r(\text{generation}, z)$ (seed 42) | 5-seed range (`[42, 101, 7, 1234, 999]`) |
|---|---|---|---|
| Inside-out (later-born cells migrate to more superficial layers) | `np.minimum(cell_type, 2) * layer_scale` | `+0.872` ($p = 3.41 \times 10^{-78}$) | `+0.872` to `+0.909` (mean `+0.890`) |
| Outside-in (later-born cells remain in deeper layers) | `(2 - np.minimum(cell_type, 2)) * layer_scale` | `-0.872` ($p = 3.41 \times 10^{-78}$) | `-0.909` to `-0.872` (mean `-0.890`) |


# Page: use-cases/connectome-constrained.md

## Connectome-constrained development (planned)

**Idea.** Public connectomes give, for a whole nervous system or a part of it, every neuron's type and position and
the number of synapses between pairs of neurons. The toolbox will use them in two ways:

1. **Supervised import:** a subset of a connectome (filtered by cell type, region or neuron ids) becomes a network,
   simulated directly.
2. **Statistical targets for development:** cell counts per type, type × type synapse matrices, degree
   distributions, transmitter composition and topography (e.g. inputs from the same or neighbouring column) become
   targets that grown circuits are fitted to. Matching is statistical, at the cell-type level, never neuron by
   neuron; when several individuals exist, their spread sets the tolerance.

**What it builds on** (roadmap phases K, I, P, N, now implemented; see the tutorial on connectome data):
- larger networks (array-backed synapse store, 32-bit neuron indices, multi-word spike history for long delays);
- a dataset catalogue and streaming importer;
- transmitter → effect as data that depends on the receptor, the organism and the developmental stage
  (e.g. glutamate acting through inhibitory glutamate-gated chloride channels in invertebrates; GABA depolarising
  early in development);
- branching axons and dendrites, with delays from path length along the arbor, and skeleton import.

**First target.** A columnar motion-vision subcircuit of the fly optic lobe: Mi1, Tm3, Mi4 and Mi9 neurons
projecting onto T4 neurons, with hexagonal column coordinates as a hand-made field. An analysis of the FlyEM male CNS
connectome v1.0 (CC-BY 4.0) found Mi1 → T4 the strongest of these inputs (about 100–115 thousand synapses onto each
T4 subtype), followed by Tm3, Mi9 and Mi4.

> **PLACEHOLDER.** The full example once phases I and P are merged: dataset download, the imported subset in the viewer, the grown circuit next to it, and the comparison of their type-level statistics.


# Page: use-cases/developmental-hypotheses.md

## Testing developmental hypotheses

Because development is explicit and seeded, one can ask "what if" questions and test them across many developmental
seeds:

| Manipulation | How |
|---|---|
| Remove a cell population or a projection | `knockout(genome, regions=[...], projections=[...])` |
| Remove a guidance cue | delete or neutralise its gene; rules are inactive without their genes |
| Change the timing of a stage | reorder the schedule, change `repeat` or `when` |
| Block spontaneous activity | remove or silence the `refine_by_activity` stage |
| Change a cell type's resting state | `genome["tonic"][region]` (standing currents) |
| Change transmitter polarity during development | planned (phase P: stage-dependent pharmacology) |

A list of well-characterised developmental steps, with the questions the toolbox could address, is in
developmental questions.

Results are statements about the simulation: "in this simulation, removing X abolishes Y across 95 / 100 seeds", not
claims about the animal.

> **PLACEHOLDER.** A worked knockout study on the topographic-map example (which genes are necessary for topography, measured across held-out seeds), with figures.


# Page: use-cases/retinothalamic.md

## Retina → relay: a small topographic-map experiment

```raw
<div class="ng-question-box">
  <p><strong>Guiding Question:</strong> <em>How does the retinotopy of the retina align with that of the lateral geniculate nucleus (dLGN) before the onset of vision—and what are the respective roles of graded molecular cues (Eph/ephrin) and patterned spontaneous retinal waves in wiring and refining this map?</em></p>
</div>
```

This worked example grows a toy retinal projection into a relay, then asks whether the map depends on EphA guidance
and activity-driven refinement. It demonstrates how to turn a developmental idea into a model and compare conditions.
The results describe **this simulation**, not measured biology. Step through the interactive carousel below to
inspect each stage of the `examples/retinothalamic.py` pipeline (E14–P14), the CLI command that advances the
simulation from one stage checkpoint to the next, and the primary developmental literature.

### Main result

| Question | Result in this simulation |
|---|---|
| Does the EphA knockout disrupt the x map? | Yes. In the p2A-shaped relay, r_x falls from 0.832 in WT to 0.013. The y map remains (r_y 0.769). |
| Does activity deprivation blur the map? | Only weakly in this setup: the change is smaller than the across-seed variation. With 100 seeds it is a small but consistent shift (topographic error +0.003, higher than WT in 86–87 of 100 seeds), see larger-seed rerun. |
| Does relay shape matter? | The equal-volume box has stronger map scores than p2A. Its smaller dimensions also change spatial extent, so this does not isolate shape alone. |

### Read the animation

The animation shows seven development milestones, then the final wild type and an EphA knockout with the same seed.
The **left panel** is the network in space: retina at the bottom, relay above it; circles are retinal cells, squares
are relay cells, and triangles are relay interneurons. Lines show a sample of retinal synapses. **Colour follows
retinal x** from purple to yellow. Relay cells inherit the weighted mean colour of their retinal inputs. In the **right
panel**, each dot is one retinal ganglion cell (RGC): x position in the retina is
plotted against the mean x or y position of its relay targets (blue = x/EphA axis, orange = y/EphB axis). A rising
diagonal means that axis is mapped in order. The knockout keeps the orange trend and loses the blue one.



*Proof of principle (WT, seed 0, run 2 below): one frame per development checkpoint, then a turn around the final
network and the same seed with EphA knocked out. Left: retina (bottom: grey progenitors and other cells, circles RGCs)
and relay (top: squares relay cells, triangles interneurons); RGCs, their axons and relay cells are coloured by retinal
x (relay cells: weighted mean over their retinal inputs). Right: each RGC's retinal position against the mean relay
position of its targets, per axis; a diagonal is a topographic map. Made by `docs/_scripts/make_media.py retino`.*


The hypotheses, metrics and pass criteria were written before the runs: 2026 11 02 retinothalamic.

    python examples/retinothalamic.py --cpu --seeds 10 --out out_dir      # about 1.5 min on 2 CPU threads
    python examples/retinothalamic.py --cpu --seeds 2 --small             # test configuration, about 10 s

### Model

```raw
<div class="ng-retino-carousel">
  <div class="ng-carousel-header">
    <div class="ng-nav-row">
      <div class="ng-counter-group">
        <button id="ng-prev-btn" class="ng-nav-btn" title="Previous stage (Left arrow)">&larr;</button>
        <span id="ng-stage-counter">Stage 1 / 12</span>
        <button id="ng-next-btn" class="ng-nav-btn" title="Next stage (Right arrow)">&rarr;</button>
      </div>
      <div class="ng-toggle-group">
        <button id="ng-toggle-box" class="ng-toggle-btn active" title="Box Relay geometry (Run 2)">Box Relay (Run 2)</button>
        <button id="ng-toggle-p4" class="ng-toggle-btn" title="P4 Atlas Relay p2A geometry (Run 3)">P4 Atlas Relay p2A (Run 3)</button>
      </div>
    </div>
    <div id="ng-pills-container" class="ng-pills-bar">
      <!-- pills populated by JS -->
    </div>
  </div>

  <div class="ng-carousel-body">
    <div class="ng-visual-pane">
      <img id="ng-carousel-img" src="../_static/media/retino_stage_01.png" alt="Development Stage Visual" />
    </div>
    <div class="ng-info-pane">
      <div class="ng-stage-title-row">
        <h3 id="ng-stage-title">1. Progenitor Seeding & Proliferation</h3>
        <span id="ng-stage-age">E14–E15</span>
      </div>
      <div class="ng-operator-tag">
        Operator: <code id="ng-stage-operator">seed_cells &rarr; proliferate</code>
      </div>
      <div class="ng-info-section">
        <h4>Model Details</h4>
        <p id="ng-stage-details"></p>
      </div>
      <div class="ng-info-section">
        <h4>Tissue State Changes</h4>
        <p id="ng-stage-changes"></p>
      </div>
      <div class="ng-info-section">
        <h4>Parameters</h4>
        <pre><code id="ng-stage-params"></code></pre>
      </div>
    </div>
  </div>

  <div class="ng-cli-banner">
    <pre><code id="ng-cli-code"></code></pre>
    <button id="ng-cli-copy-btn" title="Copy CLI command to clipboard">Copy</button>
  </div>

  <div class="ng-citation-footer">
    <strong>Key Reference(s):</strong>
    <div id="ng-citation-text"></div>
  </div>
</div>
```

Two `Box` regions (a 100 × 100 retinal sheet, a 60 × 60 × 20 relay nucleus; in run 1 placed far away so that the retinal waves,
which spread through space, never reach it) and analytic x / y gradient fields. In run 3, the relay is shaped from the
p2A alar thalamus mask of the Allen P4 Reference Atlas.

Conditions (genome values, `condition_genome`): **WT**; **b2KO**: no waves, RGCs fire as independent Poisson sources
at the same mean rate as one wave gives (one spike per RGC per 300-tick window); **TTX**: no refinement stages (no
spikes); **EphAKO**: x chemoaffinity gain 0. Click any condition name in the results tables below to jump the interactive
carousel directly to the perturbed developmental stage.

### Results, run 1 (as pre-registered; 10 development seeds per condition, seeds 0–9, mean ± SD)

| Condition | Mosaic NND/SD | r_x | r_y | Topographic error | Corner separation |
|---|---|---|---|---|---|
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(1)">WT</span>     | 2.77 ± 0.53 | 0.910 ± 0.021 | 0.907 ± 0.031 | 0.232 ± 0.016 | 0.700 ± 0.078 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(9)">b2KO</span>   | 2.77 ± 0.53 | 0.913 ± 0.018 | 0.912 ± 0.025 | 0.233 ± 0.014 | 0.687 ± 0.087 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(9)">TTX</span>    | 2.77 ± 0.53 | 0.914 ± 0.021 | 0.911 ± 0.027 | 0.234 ± 0.015 | 0.684 ± 0.091 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(6)">EphAKO</span> | 2.77 ± 0.53 | 0.021 ± 0.159 | 0.911 ± 0.025 | 0.396 ± 0.020 | 0.655 ± 0.097 |

Paired differences from WT (same seed), mean ± SD: TTX topographic error +0.0029 ± 0.0026 (higher in 9 of 10 seeds),
separation −0.017 ± 0.024 (lower in 6 of 10); b2KO error +0.0019 ± 0.0033 (7 of 10), separation −0.013 ± 0.020
(7 of 10); EphAKO error +0.164 ± 0.021 (10 of 10), r_x −0.889 ± 0.166. Per-seed values: `retinothalamic-results.json`
next to this page. About 66 RGCs, 145 relay cells and 710 retinal synapses per network.

### Which predictions held
- **H1 (mosaic unaffected): held, trivially.** The ablations act only on stages after `mosaic`, and every stage has its
  own random stream, so the mosaic is identical across conditions for a given seed. The mosaic is regular
  (NND/SD 2.8 against about 1.9 for random positions).
- **H2 (EphA-KO lowers r_x, not r_y): held.** r_x falls from 0.91 to 0.02 (|r_x| < 0.3), r_y changes by +0.004.
- **H3 (activity loss keeps the axes but blurs the map): orientation held; blurring weak.** TTX and b2KO keep
  r_x, r_y > 0.9. Their topographic error is higher and their corner separation lower than WT in the predicted
  direction, but every difference is far smaller than one pooled SD (the pre-registered criterion for "clear"), so the
  refinement effect is **weak** in this model. With a broad initial projection and only four waves, STDP followed by
  scaling and pruning changes the map little; most of the map's precision comes from the chemoaffinity term.

### Results, run 2 (after the toolbox fixes; not pre-registered)
The defects listed below were fixed in #81 and #82, and the example was changed to use the fixes: waves reach only
the retina (`waves.cells`), so the relay sits just above it (box centre z = 40 instead of 1000, `conduction_velocity`
8 instead of 200 so that delays stay about the same, trophic field threshold z > 20); `restore_modes` turns the β2-KO
Poisson drivers off after each refinement stage. Everything else, including the seeds, is unchanged. These changes
were made after run 1 was seen, so run 2 is a check that the conclusions survive the fixes, not a new test.

| Condition | Mosaic NND/SD | r_x | r_y | Topographic error | Corner separation |
|---|---|---|---|---|---|
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(1)">WT</span>     | 2.77 ± 0.53 | 0.911 ± 0.021 | 0.911 ± 0.022 | 0.230 ± 0.016 | 0.671 ± 0.088 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(9)">b2KO</span>   | 2.77 ± 0.53 | 0.913 ± 0.019 | 0.911 ± 0.026 | 0.233 ± 0.014 | 0.665 ± 0.088 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(9)">TTX</span>    | 2.77 ± 0.53 | 0.914 ± 0.021 | 0.911 ± 0.027 | 0.234 ± 0.015 | 0.655 ± 0.100 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(6)">EphAKO</span> | 2.77 ± 0.53 | 0.033 ± 0.157 | 0.911 ± 0.026 | 0.394 ± 0.019 | 0.619 ± 0.105 |

Paired differences from WT (same seed): TTX topographic error +0.0040 ± 0.0028 (higher in 9 of 10 seeds), separation
−0.015 ± 0.027 (lower in 7 of 10); b2KO error +0.0029 ± 0.0027 (8 of 10), separation −0.006 ± 0.019 (7 of 10);
EphAKO error +0.164 ± 0.022 (10 of 10), r_x −0.878 ± 0.163. Per-seed values: `retinothalamic-results-run2.json`.
The conclusions of run 1 hold: H2 held, and H3 held in orientation with a **weak** blurring (every difference far
smaller than one SD).

### Results, map read from expression (pre-registered variant, 2026-11-03)
In runs 1–4 the graded EphA / EphB genes are computed but the map comes from `B`. In this variant
(`--map-source expression`) the `expression_gradient` target rule reads them: `B = 0` on x and y, EphA sets the x
target and EphB the y target (gain 2, attraction, min-max normalised over the RGCs), and EphAKO sets the EphA gene to 0
in every cell (`express(knockout=["EphA"])`) instead of setting a gain to 0. The hypotheses were written and committed
before the variant was ever grown (no pilot run): 2026 11 03 retinothalamic expression. Same
seeds 0–9, genome and metrics as run 2, which was rerun on the same commit as the comparison and reproduced the run 2
table exactly.

    python examples/retinothalamic.py --cpu --seeds 10 --map-source expression

| Condition | Mosaic NND/SD | r_x | r_y | Topographic error | Corner separation |
|---|---|---|---|---|---|
| WT     | 2.77 ± 0.53 | 0.896 ± 0.021 | 0.889 ± 0.033 | 0.254 ± 0.013 | 0.763 ± 0.078 |
| b2KO   | 2.77 ± 0.53 | 0.890 ± 0.027 | 0.891 ± 0.032 | 0.257 ± 0.012 | 0.760 ± 0.076 |
| TTX    | 2.77 ± 0.53 | 0.896 ± 0.020 | 0.891 ± 0.034 | 0.257 ± 0.011 | 0.758 ± 0.079 |
| EphAKO | 2.77 ± 0.53 | 0.031 ± 0.074 | 0.890 ± 0.031 | 0.400 ± 0.017 | 0.690 ± 0.075 |

Paired differences (same seed), mean ± SD. Against the `B` variant (run 2), WT: topographic error +0.023 ± 0.010
(higher in 10 of 10 seeds; pooled SD 0.014), r_x −0.015 ± 0.018, r_y −0.022 ± 0.017 (lower in 9 of 10), corner
separation +0.092 ± 0.057 (higher in 10 of 10). Within the variant, against its WT: TTX error +0.0034 ± 0.0035 (higher
in 8 of 10), separation −0.005 ± 0.021 (lower in 6 of 10); b2KO error +0.0033 ± 0.0024 (10 of 10), separation
−0.003 ± 0.024 (5 of 10); EphAKO r_x −0.866 ± 0.087 (10 of 10), r_y +0.001 ± 0.019, error +0.146 ± 0.014 (10 of 10).
The mosaic is identical to run 2 for every seed and condition; about 676 retinal synapses per network (521 in
EphAKO). Per-seed values: `retinothalamic-results-expression.json`.

Predictions: E1 (both axes kept in WT, b2KO, TTX) held; E2 (EphAKO |r_x| < 0.3, r_y within 0.1 of WT) held; E3
(higher error than `B` by more than one pooled SD, r within 0.1) held; E4 (activity effects in the predicted direction
but below one SD) held as weak, with the separation drop smaller than in runs 1–3 (5–6 of 10 seeds); E5 (mosaic
unchanged) held. Not predicted: the corner separation is higher than with `B` in every seed. A likely reason, not
tested, is that the convex EphA / EphB levels pull the targets of the low corner towards one end of each axis and
spread the two corners further apart in the relay. As for the other runs, these are properties of this simulation:
the expression profiles are made up (sigmoid of an analytic gradient) and say nothing about real Eph levels.

### Relay shaped by an atlas (P4)
The relay box can be replaced by the shape of a structure from an annotation atlas, here the Allen Developing Mouse
Brain Reference Atlas at P4 (Thompson et al. 2014, Neuron): at P4 retinal axons have reached the dLGN and waves refine
the map. The atlas files are not in the repository (Allen Institute terms of use: research or other noncommercial use,
with citation). The manifest `examples/datasets/allen-devmouse-p4.json` names the official files with their checksums:

    python -m neurogenesis data fetch examples/datasets/allen-devmouse-p4.json --yes
    D=~/.cache/neurogenesis/data/allen-devmouse-p4
    python examples/allen_unpack_annotation.py $D/P4_DevMouse2012_annotation.zip     # writes annotation.mhd / .raw
    python examples/atlas_relay_mask.py --annotation $D/annotation.mhd --ontology $D/structure_graph_17.json --find geniculate
    python examples/atlas_relay_mask.py --annotation $D/annotation.mhd --ontology $D/structure_graph_17.json \
        --structure p2A --out relay_mask.npz
    python examples/retinothalamic.py --relay-mask relay_mask.npz --cpu --seeds 10

`neurogenesis.data.atlas` reads the volume and the ontology and cuts the structure (with its descendants) as a voxel
geometry; the example scales it, keeping its proportions, into its relay box (largest side 60 space units), so the atlas
sets the nucleus's **shape**, not the model's size or units. The retina stays a hand-made sheet: the eye is not part of
the brain atlas.

### Results, run 3 (P4 atlas-shaped relay; not pre-registered)
**The structure is not the dLGN.** The ontology (structure graph 17, "Developing Mouse 3") has the dorsal lateral
geniculate nucleus (`DLG`, id 16436, under p2 › Th › p2A › p2AL › DTTh › DTThm › DTThs), but the P4 3D annotation volume
labels only 77 structures at the level of the prosomeric plates (roof / alar / basal / floor plates of p1–p3, m1–m2,
the isthmus, r1–r11 and the spinal cord, plus pallial, subpallial and hypothalamic domains): no thalamic nucleus has
voxels, and `--structure DLG` finds none (the 160 µm grid annotation is the same). The relay therefore takes the shape
of the smallest labelled structure that contains the dLGN, **`p2A`, the alar plate of p2 (alar thalamus)**, id 16382:
783,455 voxels of 16.752 × 16.752 × 20 µm (4.40 mm³), one connected piece, bounding box 2965 × 3066 × 1980 µm along
the volume's axes, scaled to 58 × 60 × 39 space units around (50, 50, 60). It is a shape of the right developmental
region, much larger than the dLGN and of a different form.

Files (from the Allen download server and API, kept outside the repository):

| File | Source | Size (bytes) | SHA-256 |
|---|---|---|---|
| `P4_DevMouse2012_annotation.zip` | `download.alleninstitute.org/informatics-archive/current-release/mouse_annotation/` | 1,480,227 | `e620b6da8b22b6c36b78130183cff849f49bde8049b914d79649f730f05d157d` |
| `structure_graph_17.json` | `api.brain-map.org/api/v2/structure_graph_download/17.json` | 1,444,525 | `a5726ba0007621960625998466ffe412033c75e79c227dc6f89ea4cef96ff483` |

The zip holds `annotation.mhd` / `annotation.raw` (uint32 labels, 724 × 403 × 398 voxels, RAI). The ontology file is
an API answer; if the Allen Institute serialises it differently, its checksum changes and the manifest must be
updated. Everything else (seeds 0–9, genome, conditions) is as in run 2; about 2 min on this Mac's CPU.

| Condition | Mosaic NND/SD | r_x | r_y | Topographic error | Corner separation |
|---|---|---|---|---|---|
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(1)">WT</span>     | 2.77 ± 0.53 | 0.832 ± 0.020 | 0.808 ± 0.040 | 0.377 ± 0.015 | 0.484 ± 0.146 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(9)">b2KO</span>   | 2.77 ± 0.53 | 0.829 ± 0.024 | 0.812 ± 0.036 | 0.380 ± 0.013 | 0.473 ± 0.136 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(9)">TTX</span>    | 2.77 ± 0.53 | 0.835 ± 0.019 | 0.813 ± 0.037 | 0.380 ± 0.014 | 0.466 ± 0.136 |
| <span class="ng-cond-jump" onclick="jumpToRetinoStage(6)">EphAKO</span> | 2.77 ± 0.53 | 0.013 ± 0.061 | 0.769 ± 0.069 | 0.462 ± 0.011 | 0.487 ± 0.124 |

Paired differences from WT (same seed): TTX topographic error +0.0029 ± 0.0016 (higher in 10 of 10 seeds), separation
−0.019 ± 0.025 (lower in 7 of 10); b2KO error +0.0032 ± 0.0020 (9 of 10), separation −0.012 ± 0.023 (7 of 10);
EphAKO error +0.085 ± 0.010 (10 of 10), r_x −0.819 ± 0.067 (10 of 10), separation +0.003 ± 0.102 (lower in 5 of 10).
About 67 RGCs, 148 relay cells (154 in EphAKO) and 660 retinal synapses (383 in EphAKO) per network. Per-seed values:
`retinothalamic-results-run3-p4-atlas.json`.

In this simulation the conclusions of runs 1 and 2 hold in the atlas-shaped relay: H2 held (r_x falls to 0.01, r_y
changes by −0.04), and H3 held in orientation with a **weak** blurring (every difference far smaller than one SD). The
map is less precise than in the box (r_x 0.83 against 0.91, separation 0.48 against 0.67). The topographic error is
not comparable with runs 1 and 2: it is measured in the relay's bounding box, which the irregular shape does not fill.

### Shape control, run 4 (paired, 10 seeds; not pre-registered)
To check whether the run-3 change is only a consequence of the relay's lower volume and density, the same seeds and
conditions were run in a rectangular box with the same volume as p2A and the same bounding-box aspect ratio. It uses
the same number of relay cells, so its mean cell density matches p2A. Its dimensions are 36.28 × 37.51 × 24.23 space
units, centred at (50, 50, 60), against p2A's 58 × 60 × 39 bounding box. This is a volume and density control; its
smaller extents mean the comparison does not isolate irregularity from spatial extent. Apoptosis leaves slightly
different final counts: WT has 147.5 ± 5.2 relay cells in p2A and 145.9 ± 5.6 in the box; EphAKO has 154.4 ± 1.8 and
147.6 ± 3.8, respectively.

The legacy *topo_error* remains the error from the bounding-box ideal target. *topo_error_feasible* instead projects
each ideal target onto the closest x-y coordinate occupied by the relay mask (any depth), then measures the distance
from the observed weighted target centroid to that attainable point. For a box, all ideal coordinates are attainable,
so the two metrics are equal. This metric describes fit to the mask's available locations; it does not establish a
biological map quality threshold.

| Geometry | Condition | r_x | r_y | Bounding-box error | Feasible-target error | Corner separation |
|---|---|---:|---:|---:|---:|---:|
| p2A | WT | 0.832 ± 0.020 | 0.808 ± 0.040 | 0.377 ± 0.015 | 0.286 ± 0.011 | 0.484 ± 0.146 |
| p2A | b2KO | 0.829 ± 0.024 | 0.812 ± 0.036 | 0.380 ± 0.013 | 0.289 ± 0.010 | 0.473 ± 0.136 |
| p2A | TTX | 0.835 ± 0.019 | 0.813 ± 0.037 | 0.380 ± 0.014 | 0.289 ± 0.010 | 0.466 ± 0.136 |
| p2A | EphAKO | 0.013 ± 0.061 | 0.769 ± 0.069 | 0.462 ± 0.011 | 0.377 ± 0.010 | 0.487 ± 0.124 |
| Matched box | WT | 0.912 ± 0.020 | 0.908 ± 0.029 | 0.231 ± 0.016 | 0.231 ± 0.016 | 0.636 ± 0.095 |
| Matched box | b2KO | 0.913 ± 0.020 | 0.912 ± 0.025 | 0.234 ± 0.014 | 0.234 ± 0.014 | 0.636 ± 0.094 |
| Matched box | TTX | 0.914 ± 0.020 | 0.912 ± 0.026 | 0.234 ± 0.014 | 0.234 ± 0.014 | 0.629 ± 0.095 |
| Matched box | EphAKO | 0.034 ± 0.140 | 0.910 ± 0.025 | 0.394 ± 0.018 | 0.394 ± 0.018 | 0.596 ± 0.111 |

Paired matched-box minus p2A differences in the feasible-target error were −0.055 ± 0.022 for WT (box lower in 10/10
seeds), −0.055 ± 0.021 for b2KO (10/10), and −0.055 ± 0.020 for TTX (10/10). Thus, p2A retains the qualitative
EphA knockout result, while the map metrics are consistently weaker than in the equal-volume box under these model
settings. Because the control is smaller in each dimension, this difference may reflect spatial extent as well as
shape. Per-seed results and paired differences are in the run-4 results JSON.

Reproduce with: python examples/retinothalamic.py --relay-mask relay_mask.npz --compare-matched-box --cpu --seeds 10.
This writes both geometries' per-seed data to stats.json.



*Run 3, WT seed 0, with the relay grown in the p2A shape from the Allen P4 annotation (scaled into model space). Generated
by setting RETINO_RELAY_MASK=relay_mask.npz and running docs/_scripts/make_media.py retino.*

Atlas: Allen Developing Mouse Brain Atlas, developingmouse.brain-map.org (© 2008 Allen Institute for Brain Science);
Thompson CL et al. (2014) A high-resolution spatiotemporal atlas of gene expression of the developing mouse brain.
Neuron 83:309–323.

(retino-100-seeds)=
### Larger-seed rerun (100 seeds; not pre-registered)
The run-2 settings were rerun for seeds 0–99 (separation: 99 seeds, it is undefined for seed 87), and seeds 0–9 reproduce
the committed values. The statistics were chosen after the 10-seed results, so they are exploratory. The pre-registered
verdicts do not change (H1, H2 held; H3 orientation held, blurring **weak**).

| Condition | r_x | r_y | Topographic error | Corner separation |
|---|---:|---:|---:|---:|
| WT | 0.921 ± 0.018 | 0.922 ± 0.019 | 0.227 ± 0.018 | 0.621 ± 0.103 |
| b2KO | 0.924 ± 0.017 | 0.923 ± 0.020 | 0.230 ± 0.018 | 0.616 ± 0.100 |
| TTX | 0.924 ± 0.018 | 0.924 ± 0.019 | 0.230 ± 0.018 | 0.612 ± 0.100 |
| EphAKO | 0.000 ± 0.131 | 0.913 ± 0.022 | 0.399 ± 0.019 | 0.589 ± 0.106 |

Paired differences from WT (same seed): the topographic-error shift of TTX (+0.0029, dz +1.06) and b2KO (+0.0031,
dz +1.19) is about 0.17 of the across-seed SD, yet consistent within a seed (higher in 86–87 of 100 seeds, Wilcoxon p
below 1e-13). The corner-separation drop is detectable for TTX (−0.0096, dz −0.37, p 0.002) and not for b2KO (−0.0049,
dz −0.17, interval includes 0). A paired comparison needs about 8–10 seeds for the topographic-error shift; an unpaired
one would need more than 500 per group. Future runs should therefore compare conditions seed by seed. The expression
variant (50 seeds) gives the same picture. The per-seed data and the driver script are not in the repository.

### Limits and toolbox findings
Found while writing this example (run 1), fixed afterwards:
- `refine_by_activity` ignored its `waves` (and `twitches`) settings when run from a schedule (`run_schedule` wraps a
  dict param in `Const`); fixed in #81. Run 1 used an equivalent workaround, so its numbers are not affected.
- `grow_axons` resolved guidance values one level deep, so `B` could not hold per-axis `Gene`s; fixed in #82 (the
  example still builds `B` in Python from the condition genome).
- Waves were applied to every cell in space, so run 1 placed the relay 1000 space units away; #82 adds `waves.cells`,
  used in run 2.
- `mosaic` with a `boundary` cancelled every step of cells that started outside the geometry; fixed in #82 (the example
  still runs it without a boundary).
- `sources` left β2-KO cells as Poisson drivers after refinement (also in the final network); #82 adds
  `restore_modes`, used in run 2.

Still open:
- Runs 1–4 still take the map from the gain in `B`, and their EphA / EphB expression is illustrative. The
  `expression_gradient` target rule now reads graded genes; the variant above uses it with the EphA knockout as a null
  allele. The expression profiles and the ligand side (no ephrin gradient in the relay: the rule maps receptor level to
  a target coordinate directly) are still made up, and the rule has no competition between axons (Brown et al. 2000,
  Cell: relative, not absolute, EphA levels).
- Before the reported runs, the activity regime was set on pilot seeds 100–102 (not reported): wave drive of one
  600-unit tick (larger drive made RGCs and relay cells fire for tens of ticks), relay refractory period 5 ticks and a
  weak interneuron projection (otherwise the relay fired continuously under immature GABA), `scale` before `prune`, and
  the STDP amplitudes. These settings were chosen for a sensible firing regime, but they were tried with the outcome
  visible, so the weak H3 effect should not be read as a property of the mechanism in general.


# Page: hypotheses/2026-11-02-retinothalamic.md

## Hypothesis note: retina → thalamic relay development (written 2026-11-02, before any run)

This note is written before the example `examples/retinothalamic.py` is run. Its predictions are hypotheses about
**this simulation**, not claims about biology. The outcome is appended below, dated, after the runs.

### Question
In a toy two-region model (a retinal sheet projecting to a thalamic relay nucleus, loosely after the mouse retina →
dLGN projection between embryonic day 14 and postnatal day 14), which parts of the final map depend on molecular
guidance and which on patterned spontaneous activity? Specifically: does removing the x-axis chemoaffinity gradient,
removing correlated waves, or removing all activity change (a) the retinal ganglion cell (RGC) mosaic, (b) the
orientation of the map along each axis, and (c) its precision?

### Model (toolbox stages, see docs/STAGES.md)
Two `Box` regions (retina, relay), analytic gradient `AnalyticField`s along x and y.
1. `seed_cells` retinal progenitors, `proliferate`.
2. `express` one fate gene with lateral inhibition (neighbour weights < 0), `differentiate` high expressers into RGCs
   (the other retinal cells get another region and do not project); relay cells: relay (excitatory) and local
   interneurons (inhibitory).
3. `migrate` (small soft repulsion), then `mosaic` (homotypic exclusion of RGCs).
4. `grow_axons` retina → relay with an exuberant, broad projection (large `sigma`): target map
   `sigmoid(4·(u − 0.5)·B + c)` where the diagonal of `B` is the chemoaffinity gain per axis (x: EphA/ephrin-A-like,
   y: EphB/ephrin-B-like counter-gradients, set from genes); relay interneuron → relay local projection.
5. `apoptosis` neurotrophic (`min_inputs` on relay cells, `min_outputs` on RGCs).
6. Pharmacology profile (glutamate/AMPA-like, GABA-A-like with an `immature` depolarising context):
   `set_transmitters`, `express_receptors`, `assign_receptors`, `pharmacology` in context `immature`.
   (`transporter_switch` is not on `main`, so the chloride switch is the P3 immature → mature context.)
7. `refine_by_activity` with spontaneous propagating waves (`waves`: sources, speed, interval, duration, amplitude)
   and STDP, repeated with several wave origins.
8. `pharmacology` context `mature`, `prune` (weight threshold), `scale`, `myelinate`, `freeze`.

### Conditions
- **WT**: both gradients, propagating waves.
- **β2-KO** (stand-in for β2-nAChR knockout): no propagating waves; RGCs fire as independent Poisson sources at a low
  rate during the same refinement window (uncorrelated activity).
- **TTX**: no spiking during refinement (the activity-dependent stages see no spikes; pruning is still applied by
  weight).
- **EphA-KO**: x-axis chemoaffinity gain set to 0 (no x gradient read-out); y unchanged; waves as WT.

### Metrics (per development seed)
1. Mosaic regularity of RGCs: nearest-neighbour regularity index NND mean / SD
   (`neurogenesis.analysis.nearest_neighbour_regularity`, on x, y).
2. Per-RGC weighted target centroid: the |weight|-weighted mean local (x, y) position of its relay targets
   (vectorised with `np.bincount`). Pearson r_x (RGC local x vs centroid x) and r_y; mean topographic error: mean
   Euclidean distance between the centroid and the WT chemoaffinity target point of that RGC (local units).
3. Evoked functional separation: drive the RGCs of two opposite retinal corners (local x, y < 0.25 vs > 0.75) with
   `spiking.batch.simulate_counts`; separation = 1 − cosine similarity of the two relay response vectors.

### Predictions (hypotheses, in this simulation)
- H1: mosaic regularity is unaffected by the three ablations (they act after the mosaic stage): each ablation's mean
  within 10 % of WT.
- H2: EphA-KO lowers r_x strongly (|r_x| < 0.3) while r_y stays within 0.1 of WT.
- H3: TTX and β2-KO keep the axis orientation (r_x, r_y > 0.5 and same sign as WT) but raise the topographic error
  and lower the corner separation relative to WT (difference in means in the predicted direction, larger than one
  pooled SD for "clear", otherwise "weak").

### Seeds and reporting
At least 10 development seeds per condition (seeds 0–9, the same seeds for every condition). The table reports
mean ± SD per condition. Each prediction is reported as held / not held / weak by the criteria above, whatever the
outcome.

### Outcome (appended 2026-11-02, after the runs)
10 seeds (0–9) per condition, `python examples/retinothalamic.py --cpu --seeds 10`; table in
retinothalamic.
- H1: held (trivially: the mosaic is identical across conditions because the ablations act after it).
- H2: held: EphA-KO r_x 0.021 ± 0.159 (WT 0.910 ± 0.021); r_y 0.911 ± 0.025 (WT 0.907 ± 0.031).
- H3: orientation held (r_x, r_y > 0.9 in TTX and β2-KO). Error and separation moved in the predicted direction
  (TTX error +0.003, separation −0.017; β2-KO +0.002, −0.013, paired means) but by much less than one pooled SD:
  **weak**, not clear.
- Deviations from the plan: the β2-KO Poisson rate was set equal to the WT mean RGC rate; `scale` runs before `prune`;
  the activity regime was set on pilot seeds 100–102 before the reported runs; `refine_by_activity` needed a
  workaround for its `waves` param (see the use case).

### Run 2 (appended 2026-11-02, after the toolbox fixes #81 and #82)
Not part of the pre-registered plan: the model layout was changed after run 1 was seen (waves restricted to the
retina, relay moved next to it, β2-KO drivers turned off after refinement). Same seeds and metrics. H2 held
(EphA-KO r_x 0.033 ± 0.157, r_y 0.911 ± 0.026); H3 orientation held, blurring still weak (TTX error +0.004,
separation −0.015; β2-KO +0.003, −0.006, paired means). Details in retinothalamic.

### Larger-seed rerun (appended 2026-11-04, after the runs; not part of the pre-registered plan)
The criteria above were not changed. The statistics below (paired tests, bootstrap intervals, power) were chosen after
the 10-seed results were known, so they are exploratory.
- **Seeds.** Run-2 settings: 100 seeds (0–99) per condition. Separation is undefined for seed 87 (all four conditions:
  every response vector of one corner stimulus was zero), so separation uses n = 99; every other metric uses n = 100.
  Seeds 0–9 reproduce the committed per-seed values (largest difference 2.8e-17). The run used a driver outside the
  repository that calls the same `build_project`, `run_schedule` and `measure`; its per-seed CSVs are not committed
  here.
- **Verdicts unchanged.** H1 held, H2 held (EphA-KO r_x 0.000 ± 0.131, r_y within 0.009 of WT), H3 orientation held
  (r_x, r_y 0.924 in TTX and β2-KO), H3 blurring **weak** by the one-pooled-SD rule: topographic error +0.0029 (TTX) and
  +0.0031 (β2-KO), ratio to the pooled SD 0.16 and 0.18; separation −0.0096 and −0.0049, ratio −0.09 and −0.05.
- **What 100 seeds add.** The topographic-error shift is small against the across-seed SD (0.018) but consistent within a
  seed: paired dz +1.06 (TTX, 95% CI 0.85 to 1.35) and +1.19 (β2-KO, 0.96 to 1.48), Wilcoxon p below 1e-13, error higher
  than WT in 86–87 of 100 seeds. "Weak" is therefore not a small-sample artefact, and it is not zero. The corner-separation
  drop is detectable for TTX (dz −0.37, p 0.002) and not for β2-KO (dz −0.17, interval includes 0). p-values are
  uncorrected.
- **Seeds needed.** For 80% power with a paired comparison: about 8–10 seeds for the topographic-error shift, about 60
  (TTX) to 280 (β2-KO, very uncertain) for separation. An unpaired comparison of the topographic-error shift would need
  about 510–590 seeds per group. Future runs should compare conditions seed by seed.
- **Means moved a little.** 100-seed WT separation is 0.621 against 0.671 at 10 seeds (seeds 0–9 happen to be high, about
  0.5 SD); r_x 0.921 against 0.911.


# Page: hypotheses/2026-11-03-retinothalamic-expression.md

## Hypothesis note: retina → relay map read from expression (written 2026-11-03, before any run)

This note is written before the variant `python examples/retinothalamic.py --map-source expression` is run (no pilot
run either: the variant has only been built, not grown). Its predictions are hypotheses about **this simulation**, not
claims about biology. The outcome is appended below, dated, after the runs.

### Question
In runs 1–3 of retinothalamic the graded EphA-like (x) and EphB-like (y) genes are computed by
`express` but read by nothing: the retina → relay map comes from the gains on the diagonal of `B`, and EphA-KO sets the
x gain to 0. When the map is instead read from the expression by the `expression_gradient` target rule, and EphA-KO
removes the gene (expression 0 in every cell), does the model give the same conclusions?

### Variant (differences from run 2, everything else unchanged: seeds 0–9, genome, stages, metrics)
- `express_fate` names its three genes `fate`, `EphA`, `EphB`; the EphA morphogen gain is 1 in every condition.
- `grow_axons` retina → relay: `B = 0`, `rules_target = ["expression_gradient"]` with EphA on axis 0 and EphB on axis 1,
  gain 2, attraction, min-max normalisation over the projection's source cells. Target logit per axis
  4 · 2 · (e − 0.5), the same slope as `B = diag(2, 2, 0)` if e were the local position.
- EphAKO: `express(knockout=["EphA"])` sets the EphA column to 0 after each `express_fate` step. A constant gene
  normalises to 0.5, so the x target is the centre of the relay, as with a 0 gain in `B`.

The EphA level is sigmoid(−3 + 2 x) (x the local retinal position), so after min-max normalisation it is a convex,
not linear, function of x (0.32 at x = 0.5); the same for EphB on y. The expression-read target is therefore a
monotonic but distorted version of the `B` target, pushed towards the low end of each axis.

### Predictions (hypotheses, in this simulation)
- E1 (map orientation): in WT, b2KO and TTX the map keeps both axes: r_x, r_y > 0.5, positive.
- E2 (knockout): EphAKO gives |r_x| < 0.3, with r_y within 0.1 of WT of the same variant.
- E3 (distortion): the expression-read WT map has a higher topographic error than the `B` WT map of run 2 (error
  measured against the `B` target point), by more than one pooled SD; r_x and r_y stay within 0.1 of the `B` WT values.
- E4 (activity): as in runs 1–3, TTX and b2KO move topographic error up and corner separation down relative to WT of
  the same variant, by less than one pooled SD (weak).
- E5 (unchanged upstream): the RGC mosaic (NND/SD) is identical to run 2 for every seed and condition, since the
  changes act after `mosaic` and the fate gene does not read EphA or EphB.

### Seeds and reporting
Seeds 0–9 for every condition, `python examples/retinothalamic.py --cpu --seeds 10 --map-source expression`, compared
with run 2 (the `B` variant, rerun on the same commit). Mean ± SD per condition and paired differences per seed. Each
prediction is reported as held / not held / weak, whatever the outcome.

### Outcome (appended 2026-11-03, after the runs)
10 seeds (0–9) per condition, `python examples/retinothalamic.py --cpu --seeds 10 --map-source expression`, and the
`B` variant rerun on the same commit (identical to run 2); table in retinothalamic.
- E1: held: r_x, r_y between 0.889 and 0.896 in WT, b2KO and TTX.
- E2: held: EphAKO r_x 0.031 ± 0.074; r_y 0.890 ± 0.031 (WT 0.889 ± 0.033).
- E3: held: WT error 0.254 ± 0.013 against 0.230 ± 0.016 with `B` (paired +0.023 ± 0.010, pooled SD 0.014; 10 of 10
  seeds); r_x −0.015, r_y −0.022.
- E4: held as weak: TTX error +0.0034, separation −0.005; b2KO +0.0033, −0.003 (paired means), all below one SD.
- E5: held: the mosaic is identical to run 2 for every seed.
- Not predicted: the corner separation is higher than with `B` (0.763 ± 0.078 against 0.671 ± 0.088, higher in 10 of
  10 seeds).
- Deviations from the plan: none.

### Larger-seed rerun (appended 2026-11-04, after the runs; exploratory)
50 seeds (0–49) per condition, same driver as in the run-2 rerun (see the hypotheses note of 2026-11-02). Verdicts are
unchanged: E1 held (r_x 0.895, r_y 0.903 in TTX), E2 held (EphA-KO r_x 0.002, |Δr_y| 0.008), E3 held (WT error higher than
with `B` in 50 of 50 seeds, paired +0.0263 ± 0.0109, pooled SD 0.0174; r_x −0.024, r_y −0.021), E4 weak (TTX error
+0.0025, separation −0.0128; β2-KO +0.0025, −0.0073), E5 held. As with `B`, the topographic-error shift of TTX and β2-KO
is consistent within a seed (dz +1.05 and +0.97) and small against the across-seed SD (ratio 0.15).


# Page: use-cases/teaching.md

## Teaching

- **Spiking dynamics:** the viewer simulates in the browser; students change thresholds, leaks, weights and delays and
  see the raster change immediately.
- **Neuron models:** the same network with integer LIF, two-compartment and Hodgkin–Huxley neurons
  (neuron models cost); f–I curves in one call.
- **Cost of detail:** `python -m neurogenesis estimate` shows why a Hodgkin–Huxley model of a whole cortical area is
  out of reach on one machine.
- **Development and evolution:** grow the same genome with different seeds; evolve a projection and see robustness
  across seeds matter.

> **PLACEHOLDER.** Ready-to-use course material: a 2-hour practical with exercises and solutions, slides, and a hosted web demo that needs no installation.


# Page: integration/index.md

## Integrating neurogenesis-sim into other projects

| You want to... | Use | Page |
|---|---|---|
| use the toolbox from your own Python project | a pinned dependency + network fingerprints | python dependency |
| add biology without forking | plugins: rules, stages, models, geometries | rules plugins |
| reuse a circuit someone else made | modules | modules |
| drive it from another language or app | the bridge (JSON lines on stdin / stdout) | bridge |
| exchange networks with other tools | JSON, NGB1, CSR arrays | file formats |
| run grown networks in other simulators | export (planned) | other simulators |
| run large batches on a cluster | batched JAX simulation, parallel growth | cluster |


# Page: integration/python-dependency.md

## Using the toolbox as a Python dependency

### Pin a version
```toml
# pyproject.toml of your project
dependencies = ["neurogenesis-sim @ git+https://github.com/jobellet/neurogenesis-sim.git@<commit-or-tag>"]
```
Pin a commit or a tag: development results depend on the exact code that grew the networks.

### Freeze network fingerprints
Your results come from specific networks. Freeze a fingerprint of each (plan + genome + seed → network) and test it,
so that an upgrade of the toolbox that changes your networks fails your tests instead of silently changing results.

```python
import hashlib, json

def fingerprint(net):
    d = net.to_dict()
    return hashlib.sha256(json.dumps(d, sort_keys=True).encode()).hexdigest()

# tests/test_fingerprints.py
def test_networks_unchanged():
    expected = json.load(open("tests/fixtures/network_fingerprints.json"))
    for name, (plan, genome, seed) in my_networks().items():
        assert fingerprint(grow(plan, genome, seed)) == expected[name]
```

### Keep your project's content out of the toolbox
Region names, stimuli, tasks and environments belong to your project. If you need a new mechanism, write it as a
plugin in your package; if it is general, propose it upstream (see contributing).

### Determinism checklist
- Pass explicit seeds everywhere (`grow(..., seed=)`, `simulate_counts` kernel seed, evolution `island`).
- Never use global NumPy random state in code that feeds the toolbox.
- Record the toolbox commit, the JAX version and the device in your result files.


# Page: integration/bridge.md

## Driving the toolbox from other programs (the bridge)

`python -m neurogenesis.bridge` is a server that reads one JSON request per line on stdin and writes one JSON message
per line on stdout. The desktop apps use it; any program that can start a process can too.

### Python client (minimal)
```python
import json, subprocess

p = subprocess.Popen(["python", "-m", "neurogenesis.bridge"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                     text=True, bufsize=1)
ready = json.loads(p.stdout.readline())                 # {"event": "ready", "data": {"protocol": "1.0", ...}}

def call(i, method, **params):
    p.stdin.write(json.dumps({"id": i, "method": method, "params": params}) + "\n")
    while True:
        msg = json.loads(p.stdout.readline())
        if msg.get("id") == i:
            return msg

print(call(1, "ping"))
print(call(2, "models")["result"])
net = call(3, "generate", morphology="column", neurons=300, seed=1)["result"]   # {path, neurons, synapses, ...}
spikes = call(4, "simulate", ngb=net["path"], model="lif_int", ticks=200, seed=0)["result"]
```

### Other languages
- **C# / .NET:** the Windows and Linux app (`desktop/avalonia/`).
- **Swift:** the macOS app (`desktop/mac/Sources/Bridge.swift`).
- **JavaScript / Node, Julia, MATLAB, R:** start the process, write lines, read lines.

Methods cover projects, plans, development (with step-by-step checkpoints), simulation, cost estimates, f–I curves,
plasticity, evolution (long jobs with progress events) and modules. Full list and message formats:
bridge protocol.

> **PLACEHOLDER.** Small client libraries (`neurogenesis-bridge` for Node and Julia), and an example notebook driving the bridge from MATLAB.


# Page: integration/file-formats.md

## File formats

| Format | Content | Writer / reader | Spec |
|---|---|---|---|
| Project JSON (+ `.npz` sidecar) | geometry, fields, schedule, genome, plan, plugins, neuron model | `neurogenesis.project.Project` | project format |
| Network JSON | neurons and synapses of an `SNNNetwork` | `net.save_json`, `SNNNetwork.load_json` | readable; for small and medium networks |
| NGB1 | binary network: int32 indices, float64 positions, optional extension columns | `neurogenesis.bridge.ngb` | bridge protocol (section 6) |
| Module JSON (`neurogenesis-module/1`) | plan, genome, interface, validation, provenance | `Module.save`, `Module.load` | modules |
| Genome JSON | genome with `"src->dst"` projection keys | `neurogenesis.genome.to_json`, `from_json` | concepts |
| CSR arrays | `row_ptr`, `col_idx`, `weights`, `delays` | `net.to_csr()` | kernels |
| SONATA | nodes / edges HDF5, type tables, circuit config, for BMTK, NEST, NEURON, libsonata | `neurogenesis.interop` (`export_network(net, "sonata", dir)`, `import_network`) | other simulators |
| SWC | neuron morphologies (skeletons) | `neurogenesis.arbors.read_swc`, `write_swc`; connectome skeletons with simplification: `neurogenesis.data.skeletons` | stages (arbors) |

### Exchanging networks
```python
from neurogenesis.spiking.network import SNNNetwork
net = SNNNetwork.load_json("network.json")
row_ptr, col_idx, weights, delays = net.to_csr()          # NumPy arrays (presynaptic rows)
```
The CSR arrays can be loaded into SciPy (`scipy.sparse.csr_matrix((weights, col_idx, row_ptr))`), NetworkX or any
graph library.

> **PLACEHOLDER.** Further formats (roadmap phase J): NIR, PyNN scripts, NeuroML 2 / LEMS (Gleeson et al. 2010), gene-expression atlas grids as fields, NWB / neo recordings as fitting targets.


# Page: integration/other-simulators.md

## Running grown networks in other simulators

The toolbox's own kernels are deliberately simple and fast (integer LIF with bit-identical spikes across
implementations). For detailed or large-scale simulation, grown networks are handed to established simulators through
interchange formats (roadmap phase J).

### SONATA (available)
```bash
python -m neurogenesis export network.json --format sonata --out circuit/    # prints an export report
python -m neurogenesis import circuit/ --format sonata --out back.json       # back.json == network.json
```
```python
from neurogenesis.interop import export_network, import_network
report = export_network(net, "sonata", "circuit")    # needs: pip install "neurogenesis-sim[sonata]" (h5py)
print(report)                                         # exact / approximated / dropped, as JSON
net2 = import_network("sonata", "circuit")
```
The directory holds `circuit_config.json`, `network/nodes.h5`, `network/edges.h5`, the node and edge type tables and
NEST `iaf_psc_exp` parameter files: the layout of the SONATA format (Dai et al. 2020), read by BMTK (which runs NEST and
NEURON), libsonata, PyNN (`pyNN.serialization.import_from_sonata`) and Connectome-Manipulator. Every toolbox field is
also stored raw, so reading the circuit back gives the same network bit for bit (tested), and the edge indices are
checked with libsonata.

### How the integer neuron is translated
All exporters share one translation (`neurogenesis.interop.dynamics`), into a current-based exponential LIF
(NEST `iaf_psc_exp`, PyNN `IF_curr_exp`):

| Toolbox | Exported | Exact? |
|---|---|---|
| `shift_v`, `shift_s` | `tau_m`, `tau_syn = −1 / ln(1 − 2^−shift)` ms | per-tick decay exact; rounding not |
| voltage units, `threshold`, reset −256 | `V = E_L + s·v`: `V_th`, `V_reset` (default `E_L` −65 mV, `s` 15/1024 mV) | exact (a free scale) |
| weight `w` | `W = C_m·s·w·(1 − a)/(4a·τ_syn)` pA, `a = 2^−shift_s` | total charge matched; PSP shape approximate |
| delay `d` ticks | `d + 1` ms | the kernel's extra tick of latency included |
| `refractory` ticks | `t_ref` ms | close |
| Poisson and burster drivers | nodes with their rates as attributes | their spontaneous spikes are not generated elsewhere (reported) |
| integer rounding, minimum decrement, ±2048 clamp | — | not represented |

So spike times in another simulator are close to the toolbox's, not identical. The export report says this every
time.

### Other targets
| Simulator / toolchain | Route | Status |
|---|---|---|
| NEST (Gewaltig & Diesmann 2007) | SONATA through BMTK PointNet, or PyNN | SONATA available; PyNN planned (J4) |
| NEURON (Hines & Carnevale 1997) | SONATA through BMTK; NeuroML 2 | NeuroML 2 planned (J5) |
| Brian 2 (Stimberg et al. 2019) | PyNN backend | planned (J4) |
| Arbor (Abi Akar et al. 2019) | NeuroML 2 / morphologies (phase N) | planned |
| SpiNNaker (sPyNNaker, Rhodes et al. 2018) | PyNN | planned (J4) |
| snnTorch, Norse, Lava-DL, SpiNNaker2 | NIR (Pedersen et al. 2024): small modules only, no per-synapse delays | planned (J3) |

> **PLACEHOLDER.** Cross-simulator checks (J6): the same grown networks simulated in the toolbox and, through SONATA and PyNN, in NEST and Brian 2, with spike statistics compared against declared tolerances.


# Page: integration/cluster.md

## Large batches and clusters

- **Batched simulation.** `simulate_counts(nets, stimuli, ...)` pads and stacks many networks and runs them in one JAX
  call, on CPU or GPU. Use it for populations in evolution.
- **Parallel growth.** `neurogenesis.development.parallel.grow_many(calls)` grows many (plan, genome, seed) calls in
  worker processes (`set_workers(n)` sets their number).
- **Islands.** Run one evolution island per process or per job (`island=0, 1, ...`; the development seeds of islands
  never overlap), then `choose` among the final populations on validation seeds.
- **Cost first.** `python -m neurogenesis estimate` (or `neurogenesis.cost.check`) before submitting large jobs; the
  calibration cache is per machine, so calibrate on the compute node.

```bash
# one island per SLURM array task
#SBATCH --array=0-7
JAX_PLATFORMS=cpu python scripts/evolve.py --island $SLURM_ARRAY_TASK_ID --out runs/island_$SLURM_ARRAY_TASK_ID
```

> **PLACEHOLDER.** A complete cluster recipe (SLURM and a cloud alternative): environment setup, GPU selection, checkpoint / resume of evolution, gathering islands, and measured scaling of simulate_counts with the number of networks.


# Page: reference/cli.md

## Command-line reference

### `python -m neurogenesis`

| Command | Purpose | Key options |
|---|---|---|
| `generate` | preset network (column, layered, microcircuit) | `--neurons --morphology --exc-ratio --pace-ratio --radius --height --layers --conduction-velocity --weight-scale --inh-scale --refractory --seed --out` |
| `simulate` | run the integer reference kernel | `--network --ticks --raster --seed` |
| `stats` | topology and degree statistics | `--network` |
| `export-presets` | three preset networks for the viewer | `--out-dir` |
| `estimate` | memory and time per neuron model; with `--project`, of a development schedule stage by stage | `--neurons --synapses-per-neuron --seconds --model (repeatable) --project --no-calibrate` |
| `fit` | fit a Project's `Fit` params to data targets (CMA-ES) | `--project --targets --seeds --iterations --seed --out` |
| `validate` | rerun a module's validation assays (exit 1 on failure) | `module.json --assays` |
| `install` | install a module into the local catalogue | `module.json --catalogue --force` |
| `modules` | list installed modules | `--catalogue` |
| `data info` / `data fetch` | dataset size, license, citation; resumable, checksum-verified download | `dataset --manifest --target --yes` |
| `profile check` / `profile show` / `profile report` | check that every parameter of a profile has a status and a source (exit 1 on failure); list parameters with their status; what a project's parameters rest on (`metadata.profiles`, genes, `Fit`) | `profiles --all --online`, `project` |
| `ui-schema` | export the versioned UI schema JSON for the desktop apps | `--project --calibrated` |
| `export` | write a network (JSON or NGB1) in another format; prints the export report | `network --format {sonata} --out` |
| `import` | read a network from another format into network JSON | `path --format {sonata} --out` |

Every command has `--help`. Worked examples: cli.

### `python -m neurogenesis.bridge`
JSON-lines server for apps and other programs. No options; see bridge protocol.

### Environment variables
| Variable | Effect |
|---|---|
| `JAX_PLATFORMS=cpu` | force JAX onto the CPU |
| `XLA_PYTHON_CLIENT_MEM_FRACTION` | fraction of GPU memory JAX may take |
| `NEUROGENESIS_CACHE` | calibration cache directory (default `~/.cache/neurogenesis`) |
| `NEUROGENESIS_MODULES` | module catalogue directory (default `~/.cache/neurogenesis/modules`) |
| `NEUROGENESIS_DATA`, `NEUROGENESIS_DATASETS` | dataset cache; extra manifest directory |
| `NEUROGENESIS_PROFILES` | pharmacology profile directory |

> **PLACEHOLDER.** Planned: `neurogenesis run`, `neurogenesis stages --json`, `neurogenesis data list`.


# Page: reference/stages.md

## Stage, geometry and field reference

What the registered stages (tasks C1–C12), geometries and fields (B2–B5) and sparse guidance (D2) do in the code on
`main` (2026-11-02, after the fix waves F1–F9). Roadmap items that are not implemented are marked **Not implemented**. Defaults are those of the code; a stage called without
a parameter uses them.

### Conventions
- A stage runs as `{"name": ..., "operator": ..., "params": {...}, "when": ..., "repeat": k}` in a Project's `schedule`
  (`neurogenesis.stages.run_schedule`). Each repeat gets its own random stream, seeded from (seed, stage name, repeat).
- Every parameter is a `Param` (`Const`, `Gene`, `Field`, `Fit`, `Expr`; see `neurogenesis/params.py`) or a plain value.
  A `Field` param gives per-cell values only where the stage passes cell positions to `resolve`; this is noted below as
  "per cell". Elsewhere a `Field` param resolves to the field object itself and the stage fails.
- `when` is an expression evaluated by the restricted evaluator `neurogenesis.expressions.eval_expr` (no `eval`), with
  `N`, `N_alive`, `E`, `rep`, `tissue`, `genome`, `np`.
- Units: one tick = 1 ms; positions and distances in space units (the Project's coordinates); weights and thresholds in
  integer kernel units. Delays are limited only by the storage bound of 255 ticks (multi-word spike history in all four kernels; stages accept a `max_delay` Param).
- `cost(N, E, params)` returns (bytes, ops); `neurogenesis.stages.base.schedule_cost` sums it over a schedule, and
  `neurogenesis.devcost.estimate_development` (command line: `python -m neurogenesis estimate --project p.json`)
  predicts N and E stage by stage, the peak memory and the time on this machine.
- **Neutral** means the stage leaves the tissue unchanged. Every toolbox stage called with empty params is a
  no-op (tested for every registered stage except `legacy_grow_stage`, which needs its `plan`).

### Population (C1–C3, C14, `neurogenesis/stages/population.py`)

**`seed_cells` (C1).** Adds cells: `N` sampled uniformly in a geometry or placed at one position, a `density`
in a geometry, or a point set.
| Param | Default | Meaning |
|---|---|---|
| `N` | 0 | number of cells; `N <= 0` is a no-op |
| `geometry` | none | name of a geometry in the Project (or a `Geometry` object); cells drawn with `sample_points` |
| `position` | none | (3,) position used for all cells when `geometry` is absent |
| `region`, `cell_type` | 0, 0 | integer labels of the new cells |
| `jitter` | 0.0 | standard deviation of a Gaussian offset added to each position (space units) |
| `density` | none | cells per unit volume in `geometry` (replaces `N`): a number gives round(density × volume) uniform cells; a `Field` param is sampled by thinning against `max_density` (Lewis & Shedler 1979) |
| `max_density` | none | upper bound of a density field (required with a field) |
| `max_cells` | none | upper bound on the cells added with `density` or `points` |
| `points` | none | a list of positions (2D points get z = 0), a pattern dict (`pattern`: `regular` / `jittered` / `poisson_disc`, `bounds`, `spacing` / `jitter` / `radius`), or a `PointSetField`; with a `geometry`, points outside it are dropped |

Limits: one of `geometry` / `position` / `points` is required (`ValueError` otherwise). The volume is measured by
Monte Carlo (65 536 points, about 1 % error) from the stage's random stream; flat geometries are measured by area. The
cost assumes `N = 100` (or `max_cells`) unless `N` is a constant.

**`proliferate` (C2).** Each cycling alive cell (`Tissue.cycling`) divides with probability `prob`; the daughter is appended, the mother stays.
| Param | Default | Meaning |
|---|---|---|
| `prob` | 0.0 | division probability per cell and call (per cell; an `Expr` sees `cell_type`, `generation`, `cycling`, `birth_stage`, `region`, `x`, `y`, `z` and the top-level genes); 0 is neutral |
| `jitter` | 0.1 | standard deviation of the daughter's Gaussian displacement from the mother (space units, per cell) |
| `new_type` | none | cell type of the daughters (per cell); if absent daughters copy the mother's type |
| `max_cells` | none | upper bound on alive cells; if divisions would exceed it, a random subset divides so the bound holds exactly |
| `asymmetric_prob` | 0.0 | probability that a division is asymmetric (needs `daughter_type`) |
| `daughter_type` | none | cell type of the new daughter in an asymmetric division; the mother keeps its type (Götz & Huttner 2005, Nat Rev Mol Cell Biol) |
| `expression_noise` | 0.0 | standard deviation of Gaussian noise added to both daughters' expression, clipped at 0 |
| `birth_stage` | none | birth stage given to new daughters; if absent, one more than the latest alive `birth_stage` |

Daughters copy the mother's region, expression, sign, `cycling` (`True` by default for cells from `seed_cells` and
`proliferate`) and neuron parameters and record `parent_id` and `clone_id`; both daughters get `generation = mother's + 1`
(`Tissue.generation`). Dead cells and postmitotic cells (`cycling == False`, e.g. from `place_by_hand` or
`cell_cycle_clock`) do not divide.

**`apoptosis` (C3).** Kills cells, then compacts the tissue (connectivity re-indexed).
| Param | Default | Meaning |
|---|---|---|
| `prob` | none | death probability per cell (per cell) |
| `density_radius` | none | radius (space units, per cell) for counting neighbours |
| `max_density` | 10 | cells with more than this many neighbours within `density_radius` die |
| `min_inputs`, `min_outputs` | none | neurotrophic death: cells with fewer incoming / outgoing synapses die |

All criteria are off when absent. Neighbour counts use a dense N × N distance matrix for `N <= 2048` and the cell list
(`CellList`, D1) above. Limits: synapse counts ignore weights and activity.

**`cell_cycle_clock` (C14).** Called once per cell cycle before `proliferate` (Takahashi, Nowakowski & Caviness 1995,
J Neurosci; 1996, J Neurosci; Caviness et al. 2000). Each cycling alive cell (`Tissue.cycling`) advances its cycle
counter (`Tissue.generation`) and leaves the cell cycle (`cycling = False`) with probability
`Q(generation) = min(1, k · generation^a)` or from a per-generation table `Q` (`q_table`).
| Param | Default | Meaning |
|---|---|---|
| `k`, `a` | 0.0, 1.97 | power-law parameters for `Q(generation) = min(1, k · generation^a)` (per cell; `Const`, `Gene`, `Field` or `Expr`); `k <= 0` without `Q` / `T_C` is a no-op |
| `Q` (or `q_table`) | none | per-generation exit probability table (1-indexed by cycle `generation`) or per-cell `Param`, overriding `k` and `a` |
| `T_C` (or `tc_table`, `tc`) | none | per-generation cycle length table or formula (`Param`); advances each cycling cell's cumulative `Tissue.birth_time` |

Leaving cells get `cycling = False` and their exit `generation` (birth cycle) and `birth_time` are frozen across later
cycles, while the immediately following `proliferate` completes the current cycle's division for all cells that entered
the cycle (producing two postmitotic daughters for leaving progenitors and two cycling progenitors for self-renewing
ones). Reference neocortical profile: `examples/profiles/cell_cycle_clock.json`.

### Migration and sorting (C4–C5, `neurogenesis/stages/migration.py`)

**`migrate` (C4).** Moves alive cells for `steps` sub-steps per call (Marín & Rubenstein 2003, Annu Rev Neurosci).
| Param | Default | Meaning |
|---|---|---|
| `steps` | 1 | sub-steps per call; `speed` and `chemotaxis` are distances per sub-step |
| `speed` | 0.0 | step length along `direction` (space units, per cell); 0 is neutral |
| `direction` | (1, 0, 0) | step direction (per cell, e.g. a vector field), normalised |
| `radial` | False | step away from `target` instead of along `direction` (per cell) |
| `target` | (0, 0, 0) | centre for radial steps |
| `chemotaxis_field`, `chemotaxis` | none, 0.0 | move along the unit gradient of a named scalar field (central differences, step `gradient_step`, default 1e-3); negative values move down the gradient |
| `layer_field`, `target_layer` | none, 0 | a cell stops once its layer label (first column of the field, e.g. `LayerStackField`) equals `target_layer` |
| `min_distance` | 0.0 | soft repulsion through the cell list: each cell of a pair closer than this moves half the overlap |
| `stop_field` | none | evaluated at the new position; True or a value > 0 cancels the step for that cell |
| `boundary` | none | geometry name; a step that would leave it is cancelled |
| `tangential` | False | (per cell) remove the component of `direction` along the radial vector from `target`, so cells move perpendicular to it (tangential migration) |

Limits: deterministic (no random motion). Tangential steps are straight, so they drift outwards by O(speed²/r) per
step. **Not implemented:** the inside-out layering test case as a test of `migrate` alone.

**`sort` (C5).** Two modes, run in this order when both are given; with neither, a no-op.
- **Ordering:** reassigns the cells' coordinates along `axis` so that their order along it follows `key`
  (`"birth_stage"`, `"expression"` (column 0) or a per-cell array), keeping the perpendicular coordinates.
- **Differential adhesion** (Steinberg 1963, Science): for `steps` iterations, neighbours within `radius` (cell list)
  attract or repel according to `adhesion[type_i, type_j]` (negative = repulsion), at rate `rate`; cells closer than
  `contact` (default `radius / 2`) push apart. The matrix must cover every living cell's type.

| Param | Default | Meaning |
|---|---|---|
| `axis`, `key` | (0, 0, 1), `"birth_stage"` | ordering mode (active when either is given) |
| `adhesion` | none | types × types matrix (adhesion mode) |
| `radius`, `contact`, `rate`, `steps` | 0.1, radius/2, 0.1, 1 | adhesion-mode interaction radius, contact distance, rate, iterations |

Limits: no random motility, so weak cross-type adhesion gives local clumps rather than full separation.

**`mosaic` (C5).** Homotypic exclusion zones: for `steps` iterations, living cells of the same type closer than
`radius` move apart by `rate` × half their overlap (cell list), plus optional Gaussian motility `jitter` from the stage
random stream; `types` restricts it to some cell types and `boundary` keeps cells in a geometry. This raises the
nearest-neighbour regularity index of each type (Galli-Resta et al. 1997, J Neurosci); other types are ignored.
| Param | Default | Meaning |
|---|---|---|
| `radius` | 0.0 | exclusion radius (space units); <= 0 is the no-op |
| `steps`, `rate`, `jitter` | 1, 1.0, 0.0 | iterations, fraction of the overlap removed, motility std |
| `types`, `boundary` | all, none | cell types that interact; geometry that cells may not leave (cells already outside it move freely) |

### Expression and differentiation (C6–C7, C15, `neurogenesis/stages/differentiation.py`)

**`express` (C6).** One gene-regulatory step on the expression matrix (N, K):
`E ← E·(1 − decay·dt) + sigmoid(W·E + Σ_m f_m·w_m + W_n·Σ_neighbours E)·dt`.
| Param | Default | Meaning |
|---|---|---|
| `K` | 0 | number of genes; `K <= 0` (or no params) is a no-op; expression is padded or cut to K columns |
| `weights` | zeros | K × K regulatory weights `W` (or K² values) |
| `morphogens` | [] | list of `{"field": Param, "weights": [K]}`; the field is evaluated at the cell positions; each weight may be a `Param` (e.g. a `Gene`) |
| `radius` | 0.0 | contact radius (space units) for neighbour input through the cell list; 0 disables it |
| `neighbor_weights` | zeros | K × K weights `W_n` applied to the **sum** of neighbours' expression |
| `dt`, `decay` | 1.0, 0.0 | step size and linear decay rate |
| `activation` | `"sigmoid"` | `"sigmoid"`, or `"hill"`: x⁺ⁿ / (kⁿ + x⁺ⁿ) of the positive part x⁺ of the input |
| `hill_n`, `hill_k` | 2.0, 1.0 | Hill coefficient and half-activation input |
| `names` | unset | K distinct gene names stored in `Tissue.expression_names` (read by the `expression_gradient` rule); unset leaves the names as they are |
| `knockout` | [] | genes (names or column indices) set to 0 in every cell after the step (a null allele) |

Dead cells are kept (only the runner compacts). Limits: with `K > 0` the step is not a no-op even with zero weights (sigmoid(0) = 0.5 is
added); weight matrices of the wrong shape are silently replaced by zeros (the Hill activation is 0 for zero input). The
cost is O(N K²) (plus the cell-list query when `radius > 0`) and uses a `Gene`'s upper bound for `K`.

**`differentiate` (C7).** Applies a rule table `types`: for each entry, in order, cells matching `condition` get the
listed properties (later entries override earlier ones).
| Entry key | Meaning |
|---|---|
| `type_id` | cell type set on matching cells (default 0) |
| `condition` | expression over `expr` (K × N, so `expr[k]`), `N`, `lineage` (cell ids), `parent`, `clone`, `cell_type`, `generation`, `cycling`, `birth_stage` (birth order), `birth_time`, `region`, positions `x`, `y`, `z`, `field_<name>` (each Project field at the cell positions; geometries give 0/1) and `geom_<name>`; absent = all cells |
| `sign`, `base_weight` | sign and weight magnitude; `base_weight` keeps the sign (Dale's principle) |
| `mode`, `shift_v`, `shift_s`, `conn_radius`, `conn_prob`, `burst_period`, `burst_duty`, `poisson_hz`, `refractory`, `role`, `compartment`, `threshold`, `region`  | neuron parameters set on the matching cells |

Every value in an entry (`sign`, `base_weight` and the neuron parameters) may be a `Param`; a `Field` value
is applied per cell. An empty or absent `types` is a no-op. Limits: `type_id` and `condition` are plain values; a
condition must give one value per cell.

**`temporal_competence` (C15).** Progressive, irreversible progenitor competence cascade (McConnell & Kaznowski 1991,
Science; McConnell 1995, Neuron; Isshiki et al. 2001, Cell). Updates competence channels in `Tissue.expression` for
cycling alive cells (`alive & cycling`, plus cells exiting in the current cycle's `cell_cycle_clock`); postmitotic cells
from earlier cycles keep their competence frozen.
| Param | Default | Meaning |
|---|---|---|
| `channels` | none (no-op) | list of cascade specs `{"channel": c, "until": Param}` (starts at `1.0`, switches irreversibly off to `0.0` when the effective driver exceeds `until`) or integer channel indices (advances monotonically via `maximum(expr[c], driver)`) |
| `thresholds` | none | optional list of `until` thresholds paired with integer `channels` |
| `driver` (or `mapping`, `target`) | `Tissue.generation` | intrinsic competence driver (`Const`, `Gene`, `Field`, `Expr` or per-generation table) |
| `environment` (or `field`) | none | optional environmental `Field` / `Param`; effective driver is `max(driver, environment)`, so an older environment advances young cycling progenitors without reversing older ones |

`differentiate` conditions read `expr[c]` (or `generation`), so one rule table assigns birth-order-dependent fates.
Reference neocortical birth-order windows: `examples/profiles/temporal_competence.json`.

### Connectivity (C8–C9, `neurogenesis/stages/connectivity.py`)

**`cluster_boutons` (C19).** Cell-type specific connection multiplicity.
| Param | Default | Meaning |
|---|---|---|
| `contacts` | none | a dict of `(pre_type, post_type)` keys to distributions (fixed int, truncated poisson dict, or prob list) |

**`grow_axons` (C8).** The guidance rules of `neurogenesis.development.rules` as a stage, per projection
(source region → target region). Target point `sigmoid(4·(x_src − 0.5)·Bᵀ + c)` in local [0, 1]³ coordinates, then
`p = p0·exp(−d²/2σ²)` over target cells, one Bernoulli draw per pair.
| Param | Default | Meaning |
|---|---|---|
| `projections` | the `guidance` keys | list of `"src->dst"` strings or pairs; regions are matched by id or through `region_map`; `[]` is the no-op |
| `guidance` | none | per projection (`"src->dst"` keys, or a list of dicts with `src`, `dst`): `B` (identity), `c` (0), `sigma` (0.1), `p0` (0.0), `w` (1.0), `wc`, `inh`, per-projection rule lists, rule genes |
| `rules_shift`, `rules_target`, `rules_probability`, `rules_weight` | [] | rule names applied in order; unknown names raise |
| `conduction_velocity` | 1.0 | space units per tick; delay = round(distance / velocity) clipped to [1, `max_delay`], with a warning when clipped |
| `max_delay` | unlimited (255, the storage bound) | ticks; also a Param of `myelinate` |
| `w_clip`, `inh_weight_factor` | 512, 1.0 | weight clip and inhibitory factor (overridden per projection by `wc`, `inh`) |
| `exact` | unset | unset: dense Ns × Nd when max(Ns, Nd) <= 2048, else sparse guidance (D2, cell list); `True`: always dense, error above 2²⁴ pairs; `False`: sparse when `sigma <= 1` |
| `region_map`, `bounds_min`, `bounds_size`, `identities` | {} | region names → ids; region boxes for local coordinates (default: the bounding box of the region's cells); per-region identities |

Guidance values are resolved at any depth, so a `Gene` may be one entry of `B`. Weights are `w · (1 or −inh) · U(0.9, 1.1)`, rounded and clipped. The stage does not read
`genome["guidance"]` (use `Gene` values inside `guidance`), and raises `ValueError` when neither `projections` nor
`guidance` is given, when a listed projection has no guidance entry, or when a region name is not in `region_map`; a
region without alive cells is a no-op for that projection. Target rules (e.g. `topographic`, `lamina`,
`expression_gradient`, which reads the source cells' named expression columns) run before the dense or sparse path,
so both paths support them. Limits: the dense path uses O(Ns·Nd) memory (only up to 2048 cells per side by default); no probability rule (not even `no_autapse`) applies unless listed; the sparse path supports only the
`identity_expression`, `no_autapse` and `source_sign` probability rules and uses a fixed cutoff of 5σ.

**`place_by_hand` (C9).** Appends cells at explicit `positions` (M, 3), with `region` (0), `cycling` (`False` by
default) and optional `cell_type`, `birth_stage`, `sign`, `mode`, `shift_v`, `shift_s`, `base_weight`, `threshold`,
`directions` (scalars or one value per cell). No positions = no-op.

**`connect_by_hand` (C9).** Appends synapses `pre[i] → post[i]` (current cell indices, not cell ids) with `weight`
(1.0, truncated to an integer) and `delay` (1 tick). Indices outside [0, N) and delays outside [1, 255] raise
`ValueError`. Empty lists = no-op. Two more supervised inputs (run before the index lists):
- `matrix`: a dense N × N weight matrix (row = pre, column = post, 0 = no synapse), allowed up to N = 2048 cells,
  with `delay_matrix` (N × N) or a scalar `delay`.
- `rules`: a list of connection rules, each `{pre, post, prob, weight, delay | velocity, max_distance, sigma, dale,
  allow_autapse}`. `pre` / `post` select living cells by a dict of `region` / `cell_type` values or a boolean
  expression over `cell_type`, `region`, `birth_stage`, `generation`, `sign`, `x`, `y`, `z`. Without `max_distance`
  the number of synapses is drawn from Binomial(N_pre · N_post, prob) and distinct pairs are drawn without building
  an N_pre × N_post array (O(E)); with `max_distance` candidates come from the cell list, each kept with `prob`
  (times exp(−d²/2σ²) with `sigma`). `velocity` sets delay = round(distance / velocity) clipped to [1, 255] with a
  warning; `dale` gives each synapse the presynaptic sign; autapses are dropped unless `allow_autapse`. Values may be
  `Param`s.

### Maturation (C10–C12, `neurogenesis/stages/refinement.py`)

**Plasticity mask (K6).** `tissue.connectivity.plastic` is an optional per-synapse bool array; absent (None, the
default) means every synapse is plastic and the stages below behave exactly as without it. Synapses with
`plastic == False` keep their weight in `refine_by_activity`, `scale` and `neuromodulate` and are never removed by
`prune` (`scale` still counts them in each cell's incoming sum). The mask follows `take` / `concat` / `append`,
`compact`, `subset`, `concatenate`; new synapses are plastic. `to_network` / `from_network` carry it only as the
in-memory attribute `_plastic` of the network (network files do not store it).

**`prune` (C10).** Removes synapses with `|w| < threshold` (default 0, neutral). With `min_coactivity`, it also
removes synapses whose presynaptic spikes, arriving after the synapse delay, were followed by a postsynaptic spike
within `window` ticks (default 5) fewer than `min_coactivity` times ("use it or lose it", Katz & Shatz 1996, Science).
Activity comes from `spikes` (a (T, N) raster) or a `lif_int` simulation of `ticks` (100) with optional
`ext_current`; dead cells are compacted first. Cost O(T · E).

**`scale` (C10).** Multiplies each cell's incoming weights so that their absolute sum equals `target`, then rounds to
integers. Without `target` it is a no-op. `rate` (default 1) moves only that fraction of the way per call
(1 + rate · (factor − 1)), a discrete time constant (Turrigiano et al. 1998, Nature). Limits: small weights can round
to 0.

**`refine_by_activity` (C11).** Simulates the tissue with the JAX `lif_int` kernel for `ticks`, then applies a plasticity
rule of `neurogenesis.spiking.plasticity` once to the weights. Dead cells are compacted first.
| Param | Default | Meaning |
|---|---|---|
| `ticks` | 100 | simulated ticks (ms) |
| `rule` | none (no-op) | `"three_factor"` (`eta` 0.01, `tau_e` 20 ticks; `modulator`: constant, (ticks,) or (ticks, N), or `modulator_cells` whose spike count per tick × `modulator_gain` is the modulator) or `"stdp"` (`A_plus` 1.0, `A_minus` 1.0, `tau_plus` 20, `tau_minus` 20 ticks, `w_min` 0, `w_max` 1000) or `"hebbian"` (`eta` 0.01, `norm_target` 1000, `norm_type` `"l1_subtractive"`) |
| `sources` (or `driver_cells`) | none | cell ids (or indices) made Poisson drivers (`mode = 1`) at `poisson_hz` (1000 Hz); this change stays in the tissue unless `restore_modes` |
| `restore_modes` | False | restore every cell's `mode` and `poisson_hz` after the stage, so drivers act only during it |
| `ext_current` | none | external current per cell (array, or `{cell id: current}`) |
| `spikes` | none | a (ticks, N) spike raster used instead of simulating |
| `waves` | none | spontaneous waves: `sources` (cell ids or points), `speed` (space units per tick), `interval`, `duration` (ticks), `amplitude` (current), `cells` (`{region, cell_type}`: the cells the waves reach; default all) |
| `twitches` | none | motor twitches with sensory feedback: `motor_cells`, `sensory_cells`, `interval`, `duration`, `amplitude`, feedback `delay` and `gain` |
| `record` | false | keep the (ticks, N) spike raster on the tissue (`_activity_spikes`, in memory only) for `competitive_elimination` |

Without `rule` the stage is a no-op . Limits: plasticity acts on magnitudes and keeps synapse signs; weights
are cast to integers. The three-factor eligibility counts arrivals that coincide with a postsynaptic spike in the same tick. **Not implemented:** pruning inside the stage (follow with `prune`), other neuron models.

**`competitive_elimination` (C22, `neurogenesis/stages/competition.py`).** Competitive synapse elimination (Willshaw
1981, Proc R Soc Lond B; Gouzé, Lasry & Changeux 1983, Biol Cybern; Balice-Gordon & Lichtman 1994, Nature). Each
input's score is its coactivity with its target (presynaptic spikes, after the delay, followed by a target spike within
`window` ticks, default 5). On every target that spiked at least `min_target_spikes` times (default 1), inputs scoring
below `fraction` (default 0 = no-op) of the best input onto that target lose `delta` (default 0) of their magnitude
(sign kept, floored at 0) or, with `remove` (default false), are removed. Silent targets change nothing; synchronous
inputs score alike and are all kept; non-plastic synapses (K6) compete but are never changed. Activity: `spikes` (a
(T, N) raster) or the raster recorded by `refine_by_activity` with `record: true`; without either it is a no-op
(`ticks`, default 100, only sizes the cost). Grouping by target: one sort and `np.maximum.reduceat`; cost O(T · E).
Differs from `prune` (absolute weight or coactivity thresholds).

**`partner_select` (C18, `neurogenesis/stages/partner.py`).** Keeps a candidate synapse with probability
`compat[i][j]` (a `Param` matrix, pre channel i × post channel j, clipped to [0, 1]). A cell's channel is its
`cell_type` (default), the argmax of its expression (`channels: "expression"`) or an explicit per-cell list; channels
outside the matrix are kept. Filters synapses from `grow_axons`, `arbor_synapses` or `connect_by_hand`; an identity
matrix reproduces the `identity_expression` rule. Neutral: no `compat` = no-op. Cost O(E). Transmitter–receptor
matching (P2) is not implemented.

**`fasciculate` (C16, `neurogenesis/stages/fasciculation.py`).** Follower axons bundle along pioneer tracts through
homophilic adhesion (L1, NCAM, Fasciclin II). For morphological arbors, active neurite growth cone nodes steer towards
nearby pioneer branch tangents within `radius`, weighted by `adhesion_matrix` [pre_type, post_type]; defasciculation
occurs where `defasciculation_field` exceeds threshold. For point-to-point connections, bundles correlate target
offsets. Neutral: no `adhesion_matrix` = no-op. Cost O(N + E).

**`choice_point` (C17, `neurogenesis/stages/fasciculation.py`).** Midline and choice points where guidance receptors or target
rules switch. When cells or growth cones enter a choice point `geometry` or `choice_field`, guidance receptor channels
are updated (e.g. switching attraction to repulsion to prevent re-crossing). For projections, routes via an intermediate
`waypoint_region`. Neutral: no `geometry`, `choice_field` or `waypoint_region` = no-op. Cost O(N + E).

**`tissue_expand` (C24, `neurogenesis/stages/expansion.py`).** Dilates the positions of living cells about a centre
(default: their centroid) by `1 + kappa * N_new / N_total` along the chosen `axes`, where `N_new` counts the cells
added since the previous call. Proliferation then pushes existing cells outward, so density stays bounded while the
count grows (in this simulation, with kappa 0.668 and growth x1.5 per cycle: about x5 per tangential axis over 8 cycles).
The first call only records the count (or give `n_prev`); call it once before the first `proliferate`. Synapses keep
their delays unless `recompute_delays` (with `conduction_velocity`, `max_delay`). Neutral `kappa` 0 = no-op. Cost O(N).

**`critical_period_gate` (C23, `neurogenesis/stages/critical_period.py`).** Opens and closes a critical period of
plasticity by setting the K6 `plastic` mask of the gated synapses (those onto `target_type` cells, all by default):
`False` before opening, `True` while open, `False` after closing. Opening when the maturity of inhibition from
`source_type` cells reaches `open_threshold`, closing after `close_after` gate calls or when maturity reaches
`close_threshold`, as maturing parvalbumin-cell inhibition opens and perineuronal nets close the ocular-dominance
critical period (Hensch 2005, Nat Rev Neurosci; Hubel & Wiesel 1970, J Physiol); `reopen` restores plasticity, as
removing the nets with chondroitinase ABC does (Pizzorusso et al. 2002, Science). Call it before each plasticity stage;
its state (phase, gate calls since opening) is kept on the tissue as `_critical_period` (not carried by `subset` /
`concatenate`). Synapses added later are plastic until the next call.
| Param | Default | Meaning |
|---|---|---|
| `source_type` | none | cell type id(s) whose inhibitory (w < 0) synapses measure maturity |
| `target_type` | all cells | cell type id(s) whose incoming synapses are measured and gated |
| `measure` | `"mean"` | `"mean"`: mean \|w\| of those synapses; `"sum"`: mean over target cells of the summed \|w\| |
| `maturity` | none | a maturity value (`Param`: gene, field, `Expr`) used instead of the inhibitory measure |
| `open_threshold` | none | opens when maturity >= this |
| `open` / `close` | false | open / close now (place the entry at the wanted stage index of the schedule) |
| `close_after` | none | closes this many gate calls after opening |
| `close_threshold` | none | closes when maturity >= this (ignored after a reopening) |
| `reopen` | false | reopens: plastic again, `close_after` counts from here |

Neutral: without `open_threshold`, `open`, `close` and `reopen` the stage is a no-op (any of them activates the gate,
so before opening the gated synapses are not plastic). Cost O(N + E). Mouse and cat ages: `examples/profiles/critical_period_gate.json`.

**`myelinate` (C12).** Multiplies delays by `factor` (alias `delay_factor`; absent = no-op; 1.0 is neutral), rounds
and clips to [1, `max_delay`] ticks (default 255) with a warning when clipped. By default every synapse is scaled; given criteria select
synapses passing all of them: `min_delay` (long projections, ticks), `pre_region` / `post_region` (lists of region
ids), `min_rate` (presynaptic rate in Hz, from `spikes` or a simulation of `ticks`; activity-dependent myelination,
Gibson et al. 2014, Science; needs a compacted tissue).

**`freeze` (C13, `neurogenesis/stages/freeze.py`).** Validates the tissue (finite positions, synapse indices in
range, delays in [1, 255], |weight| <= 32767 and, with `dale`, outgoing signs equal to the presynaptic sign) and raises
`FreezeError` listing every problem; the tissue is unchanged. With `path` it writes the compiled network (NGB1 for a
`.ngb` suffix, else JSON). The functions `freeze(tissue)` → `SNNNetwork` and `develop(project, genome, seed)` (run the
schedule, then freeze) do the same from Python.

### Arbors (N1–N3, `neurogenesis/arbors.py`, `neurogenesis/stages/arbors.py`)
`ArborStore` holds the trees of all cells as struct of arrays: `xyz`, `radius`, `parent` (−1 = root, always
`parent[i] < i`), `compartment` (0 undefined, 1 soma, 2 axon, 3 dendrite, 5 neurite, other integers are user labels)
and `cell` (index into the tissue). It lives in `Tissue.morphology` and follows `compact`, `subset` and `concatenate`.
`path_length_to_root`, `depth` (pointer jumping, O(M log depth)), `take` (drop nodes, children re-attached to the nearest
kept ancestor), `downsample(step)` and `to_budget(max_nodes_per_cell)` (keep roots, branch points, terminals and
compartment changes); `read_swc` / `write_swc` for SWC files (`scale` converts units).

**`grow_arbors` (N2).** Grows neurites from each selected cell's soma for `steps` steps (absent or <= 0 = no-op).
| Param | Default | Meaning |
|---|---|---|
| `compartment` | `"dendrite"` | label of the new nodes (`axon`, `dendrite`, `neurite` or an integer) |
| `n_neurites` | 1 | tips started per cell (per cell, read at the soma) |
| `step_length`, `angle_noise` | 1.0, 0.2 | elongation per step (space units); Gaussian turning (radians) |
| `branch_prob`, `branch_angle` | 0.0, 0.5 | branching probability per tip and step; angle of the new branch |
| `stop_prob`, `retraction_prob` | 0.0, 0.0 | stop probability per tip and step; removal probability of each new terminal at the end |
| `direction` | random | initial direction (vector, or per cell) |
| `attractor_field`, `attraction` | none, 0.0 | tips turn up the gradient of this scalar field (negative: down) |
| `stop_field`, `boundary` | none | a step into `stop_field` > 0 or out of the geometry stops the tip |
| `cells`, `soma_radius`, `max_nodes` | all, 0, 10⁶ | `{region, cell_type}` selector; radius of new nodes; bound on new nodes |

**`arbor_synapses` (N3).** Synapses from geometric proximity (Peters' rule): axonal nodes (`pre_compartments`,
default axon and neurite) within `contact_distance` of dendritic nodes (`post_compartments`, default dendrite, neurite,
soma) of another cell, found through the cell list over nodes. Each contact is kept with `p_contact` (1.0), at most
`max_per_pair` (1) per cell pair, closest first. Delay = round(axonal path length / `axon_velocity` (1.0) +
dendritic path length / `dendrite_velocity` (ignored if absent)), clipped to [1, 255] ticks with a counted warning.
Weight `weight` (1) with the presynaptic sign (`dale`, True). Each synapse records `pre_node`, `post_node`,
`path_length` and `dendritic_distance`. No arbors or no `contact_distance` is the no-op. The cost assumes 50 nodes per
cell unless `expected_nodes` is given.

### Pharmacology (P1–P3, `neurogenesis/pharmacology.py`, `neurogenesis/stages/pharmacology.py`)
A **profile** (JSON: `id`, `transmitters` (at most 7, bit k of `Tissue.transmitter`), `receptors`) maps each receptor
class (the value of a synapse's `receptor_class`) to a `transmitter`, a `sign` (+1, −1 or `"pre"`: the weight keeps
its sign), a `gain` (number or `Param`), optional `reversal_mV`, `tau_ms` (recorded for conductance models) and
`contexts` (`{context: {sign, gain}}`, e.g. depolarising GABA-A in immature neurons, Ben-Ari 2002, Nat Rev Neurosci).
`load_profile` takes a dict, a path, or an id searched in `$NEUROGENESIS_PROFILES` and `./profiles/`. The default
profile (one classic receptor, sign `"pre"`) changes no weight, so existing networks keep their fingerprints. An
example profile with cited entries is in `examples/profiles/example_receptors.json`.
- **`set_transmitters`**: `rules` = [{`condition`, `transmitters`: [names], `add`}] set each cell's transmitter bitmask
  (co-transmission: several names; Dale's principle is not enforced).
- **`express_receptors`**: `rules` = [{`condition`, `receptor`, `level` (a `Field` is read per cell)}] set
  `Tissue.receptor_expression` (one column per receptor of the `profile`).
- **`assign_receptors`**: each synapse's receptor class among the receptors whose transmitter the presynaptic cell
  releases, in proportion to their postsynaptic expression (`mode` `"sample"` from the stage stream, or `"max"`);
  synapses without an expressed candidate keep their class. O(E R).
- **`pharmacology`**: compiles weights in a `context`: sign from the receptor, magnitude × gain, and synapses whose
  presynaptic cell lacks the receptor's transmitter set to 0 (`require_transmitter`). Run it in context `"immature"`
  before `refine_by_activity` and `"mature"` after it for a sign switch during development; gains multiply at every
  call, so give gains other than 1 in one compilation only.
- **`transporter_switch`** (C21): per-cell chloride reversal potential from maturation, the NKCC1 → KCC2 switch
  (Rivera et al. 1999, Nature; Ben-Ari 2002, Nat Rev Neurosci). E_Cl = `e_immature` + (`e_mature` − `e_immature`) ·
  sigmoid((age − `midpoint` + `activity_gain` · `activity`) / `width`). Age is `age` (a `Param`) or `now` minus the
  cell's `clock` (`"birth_time"`, default, in the unit of `cell_cycle_clock`'s T_C; `"birth_stage"`, stage index;
  `"generation"`, cycles); `now` defaults to the largest clock value (youngest cell: age 0); `midpoint` (0) and `width`
  (1) are in the age's unit. `activity` (per cell, default 0: `refine_by_activity` does not record it yet) advances the
  switch by `activity_gain` (0 = purely programmed). Synapses whose `receptor_class` is in `receptors` (names in
  `profile`, or indices) get `e_rev` = E_Cl of their postsynaptic cell, used by the conductance models
  (`two_compartment`, `hh`); `lif_int` ignores `e_rev`, so with `v_rest` (mV) their weights also take the sign of
  E_Cl − `v_rest`. Run it after `pharmacology` (which rewrites `e_rev` from the profile). Without `receptors`,
  `e_immature` or `e_mature`: no-op. O(N + E). Rodent defaults (E_Cl −45 → −75 mV, switch P5–P10) with citations:
  `examples/profiles/transporter_switch.json`.
Conditions use the variables of `differentiate` conditions. Each stage without its params (`rules` / `profile`) is a
no-op. Conductance-based synapses (P4) are in the spiking models (`docs/NEURON_MODELS.md`) and neuromodulation (P5) is
the `neuromodulate` stage below. **Not implemented:** the legacy `receptor_sign` rule is not re-expressed as a profile.

### Neuromodulation and dendrites (P5, N5, `neurogenesis/neuromodulation.py`, `neurogenesis/dendritic.py`)

**`neuromodulate` (P5).** Three-factor plasticity (`spiking.plasticity.ThreeFactorRule`) over a recorded spike raster
`spikes` (T × N array, a plain value), gated by a modulator released by `modulator_cells` (volume transmission). The
level at a cell is the sum over modulator cells of their spike trains filtered with `tau_ms`, weighted by
exp(−distance / `space_constant`); dw = `eta` × eligibility × level, the eligibility summing pre-synaptic arrivals
(after the synaptic delay) that coincide with post-synaptic spikes, filtered with `tau_e`. Weights keep their sign and
their magnitude is clipped to [`w_min`, `w_max`]; synapses with `plastic == False` (K6) keep their weight. Every
parameter but `spikes` is a `Param` (a Gene-controlled `eta` is tested); a `Field` resolves to the field object, so use
scalar sources.
| Param | Default | Meaning |
|---|---|---|
| `modulator_cells` | none | cell indices releasing the modulator (none or empty = no-op) |
| `space_constant`, `tau_ms` | 50.0, 50.0 | spatial decay (space units) and time constant (ticks) of the modulator |
| `eta`, `tau_e` | 0.05, 50.0 | learning rate; eligibility time constant (ticks) |
| `tau_plus`, `tau_minus` | 20.0, 20.0 | accepted for a timing-dependent eligibility; the current rule does not use them |
| `w_min`, `w_max` | 0.0, 1000.0 | bounds of the weight magnitude |
| `ticks` | all rows | rows of `spikes` used; the cost assumes 1000 when absent |
No `spikes` or no modulator cells is the no-op. The source × target distance kernel is evaluated only at cells that
receive synapses, in blocks of at most 2²⁰ pairs; memory is O(T N) (the per-cell modulator level) and time
O(T S P + T E) for S modulator cells and P postsynaptic cells. The spike raster must be supplied (the stage does
not simulate).

**`dendritic_attenuate` (N5).** Passive dendritic filtering: each synaptic weight is multiplied by
`exp(−d / length_constant)`, where `d` is the synapse's dendritic distance (computed from `post_node` and the
morphology's path length to the soma when missing); a non-zero weight keeps a magnitude of at least 1. With
`dendrite_velocity`, `round(d / velocity)` ticks are added to the delay; with `distal_compartment_threshold`, the target
cells of synapses at or beyond that distance are marked compartment 1 (distal).
| Param | Default | Meaning |
|---|---|---|
| `length_constant` | 100.0 | dendritic length constant (space units); a `Param` |
| `dendrite_velocity` | none | space units per tick for the added dendritic delay; a `Param` |
| `distal_compartment_threshold` | none | distance (space units) beyond which targets are marked distal; a `Param` |

Empty params = no-op. Limits: synapses without a dendritic distance are left unattenuated; the added delays are still
clipped to `max_delay` (default 255 ticks), with a warning when clipped.

### Geometry (B2, `neurogenesis/geometry/mesh.py`, `voxel.py`)
- **`mesh`** (`MeshGeometry(vertices, faces, resolution=32)`, `from_obj`, `from_stl` for ASCII and binary STL).
  `contains` by ray parity, vectorised over points and faces in blocks of at most 2²⁰ pairs; `sample_points` draws
  uniformly in a `resolution`³ voxel cache (inside and surface voxels) and keeps points that pass the exact test.
  JSON stores vertices, faces and resolution as lists.
- **`voxel_mask`** (`VoxelMaskGeometry(mask, origin, pitch)`): a boolean (X, Y, Z) occupancy array; `contains` by
  lookup, `sample_points` uniform over occupied voxels. `from_npz` / `save_npz` read and write `mask` with an
  axis-aligned 4 × 4 `affine` or `origin` + `voxel_size` (a rotated affine raises); a file-backed mask serialises as
  `{"type": "voxel_mask", "npz": path}`.

### Fields (B3–B5, `neurogenesis/fields/`)
Fields are evaluated with `values(points)` and registered by type name in the Project's `fields`. `run_schedule` binds
each field to the run's genome (`Field.bind`), so `Param` values inside a field are resolved.
- **`GridField(data, origin, spacing, method="nearest")`** (B3): 2D or 3D grids, scalar or with trailing vector
  components; `nearest` or `linear` (`bilinear`, `trilinear`) sampling, clamped at the edges.
- **`AnalyticField(expr)`** (B3): an expression in `x`, `y`, `z`, `exp`, `sin`, `cos`, `pi` and whitelisted NumPy
  functions, evaluated by the restricted evaluator.
- **`VectorField`** (stacked scalar fields) and **`LabelField`** (integer labels on a grid, nearest lookup, a background
  value outside, optional names) (B3).
- **`LayerStackField(base_field, thicknesses, labels=None)`** (B3): the base field is a depth; returns `(label, w)` per
  point, `w` in [0, 1] the relative depth inside the layer. Thicknesses are numbers or `Param`s.
- **Point patterns and maps** (B4, `maps.py`): `regular_points`, `jittered_points`, `poisson_disc_points` (minimum
  distance `radius`) return point arrays; **`PointSetField`** registers a point set as a field (given `points`, or a
  `pattern` with `bounds`, `spacing` / `jitter` / `radius` and its own `seed`, regenerated identically on load); its
  values are the distance to the nearest point (cell list), and `seed_cells` accepts it as `points`.
  **`PinwheelField`** gives an orientation angle in [0, π) from singularities with charges (`pinwheel_map` returns it
  sampled on a grid); `pinwheel_charges` measures the charge around given centres; `find_pinwheels(angles, origin,
  spacing)` locates the pinwheels of any 2D angle grid (winding of 2θ around each grid square, charge ±1/2);
  `bandpass_pinwheels(..., return_pinwheels=True)` also returns the positions and charges of its pinwheels.
- **Operations** (B5, `ops.py`, NumPy only): `Add(a, b)`, `Subtract(a, b)`, `Multiply(a, b)` (fields or constants),
  `Threshold(field, threshold, below, above)`, `GaussianSmooth(grid_field, sigma)`, `DistanceTransform(bounds, spacing,
  target_points)` (distance to a point set on a grid), `Gradient` (of a grid field, or central differences for any
  field), `DistanceToBoundary` (distance to a geometry's or a label region's boundary, optionally signed, grid-based
  with error under one spacing). Composite fields pass `bind(genome)` to their sub-fields, and
  `collect_project_genes` finds `Gene`s inside field definitions. Large operations work in blocks of at most 2²⁰ pairs.

### Sparse guidance (D2, `neurogenesis/development/sparse_guidance.py`)
`sparse_guidance(ctx, plan, rng, c=5.0, exact=False)` returns `(pre, post, p)` for one projection. With `exact=True` it
computes the dense probabilities and draws exactly as the dense code. With `exact=False` it queries target cells within
`c·σ` of each target point through `CellList` (memory O(candidates)) and supports only the `identity_expression`,
`no_autapse` and `source_sign` probability rules (others raise `NotImplementedError`). Limits: the sparse mode draws
different random numbers from the dense mode, so it does not reproduce dense networks for the same seed; the legacy
`grow()` does not use it, and it is not the default for large tissues (`grow_axons` uses it only with `exact=False`).


# Page: reference/api.md

## Python API

Generated from the docstrings of the installed package.

### Development

### Projects, Params and stages

### Geometry and fields

### Genomes, evolution, modules

### Spiking

### Cost and analysis

### Interoperability

### Bridge


# Page: reference/project-format.md

## Project Format (Schema Version 1.0.0)

A `Project` in `neurogenesis-sim` represents the entire state of a developmental experiment, including geometry, generated or painted fields, the schedule of developmental stages, the genome, plugins, and metadata.

### File Structure

A project is stored on disk primarily as a JSON file. If the project contains large NumPy arrays (e.g., from dense field data or genome representations), they are extracted and saved into a sidecar `.npz` file to keep the JSON clean and small.

For example, if you save to `my_project.json`, the files created might be:
- `my_project.json`: Contains the JSON representation of the project.
- `my_project.npz` (optional): A compressed numpy archive containing any `np.ndarray` objects found in the project.

#### Array Reference Mechanism

When `Project.save()` encounters a NumPy array (`np.ndarray`), it replaces the array in the JSON tree with a dictionary containing a special reference key. The array is then saved into the `.npz` sidecar file using the value of that key as its filename/identifier within the archive.

During `Project.load()`, the JSON file is read first. Then, if the sidecar `.npz` file exists, it is loaded. The loader traverses the JSON structure, and whenever it encounters the special reference dictionary, it replaces it with the corresponding array loaded from the `.npz` file.

### Schema Fields

A valid version 1.1.0 Project dictionary must contain the following keys:

- **`version`** (`str`): The schema version. Currently `"1.1.0"`.
- **`geometry`** (`dict`): Defines the shapes in 2D or 3D, such as primitives, meshes, or voxel masks.
- **`fields`** (`dict`): Hand-made or generated data on the geometry (scalar fields like morphogen gradients, vector fields like orientation, or point sets).
- **`schedule`** (`list`): An ordered list of Stage configuration dictionaries, possibly repeated or looped.
- **`genome`** (`dict`): A nested dictionary of genes, compatible with `neurogenesis.genome`. The optional key
  `"__fit__"` holds fitted values of `Fit` params, `{target: value}` (written by `neurogenesis.fitting`); a `Fit` whose
  target is absent resolves to its `init`, so projects without this key develop exactly as before.
- **`plan`** (`dict`, optional): A legacy `BodyPlan` dictionary, used for backward compatibility.
- **`plugins`** (`list` of `str`): A list of Python module names that register rules, stages, or models to be imported on load.
- **`neuron_model`** (`str`): The neuron model used. Defaults to `"lif_int"`.
- **`metadata`** (`dict`): Any additional user or application metadata.

Optional sections, added in 1.1.0 (task K1); each is absent (or `null`) or a dict:
- **`data_sources`**: connectome datasets and their filters, `{name: {"dataset": id, "files": [...], "filters": {...}}}`
  (every entry needs a `dataset` id; Phase I).
- **`pharmacology`**: a transmitter / receptor profile reference and its parameters (Phase P).
- **`morphology`**: arbor and compartment settings (Phase N).

### Backward Compatibility

The schema includes a migration system. Older project formats (e.g., `"0.1.0"`) which consisted only of a `BodyPlan` will be automatically migrated to `"1.0.0"` by wrapping the old dictionary inside the `"plan"` field and applying defaults to the remaining required fields.

`migrate_100_to_110` changes only the version of a 1.0.0 project (the new sections are optional), so every 1.0.0 project
loads and develops exactly as before. `schema.migrate` chains single-step migrations (0.1.0 → 1.0.0 → 1.1.0).


# Page: reference/profiles.md

## Parameter profiles and provenance

A **profile** is a JSON file of default parameters for a stage or an example: a species, a brain region, an age range.
The toolbox does not depend on any profile. Every value a profile gives can be replaced by a constant, a `Gene`, a
`Field` or a `Fit` parameter, and a gene can take any value in its bounds, including combinations found nowhere in nature.
A profile says only what a sensible default is, and **where each number comes from**, so that a measurement, a
derived value, an assumption and a free parameter are never mixed up.

```
python -m neurogenesis profile check --all          # every profile of examples/profiles, ./profiles, $NEUROGENESIS_PROFILES
python -m neurogenesis profile check my_profile.json --online    # also ask Crossref whether each DOI exists
python -m neurogenesis profile show cell_cycle_clock             # parameters with their status
```

### The four statuses

| status | meaning | required |
|---|---|---|
| `measured` | a value or statement in a paper | `source`, `evidence` |
| `derived` | computed from measured values (an interpolation, a fit) | `method`, and `source` + `evidence` if it uses papers |
| `assumed` | a modelling choice or an illustrative value | `note` |
| `free` | a virtual parameter with no biological counterpart | `note`, optional `bounds` |

Every entry also has a `quantity`: what the number is. An E_GABA is not an E_Cl, and a time per embryonic day is not a
time per cell cycle. Optional fields are `species`, `region`, `age`, `method`, `note` and `bounds` (`[lo, hi]`).

`source` is a list of `"doi:10.xxxx/..."` strings or `"ref:Author Year, venue"` for work without a DOI.
`evidence` says what was actually read: `full_text`, `abstract`, `title_only` or `not_checked`. An honest `abstract` is
worth more than an unverified `full_text`.

### Where the entries go

Each top-level key of a profile, apart from the metadata keys (`id`, `description`, `citations`, `age_unit`, `notes`,
`extends`, `provenance`), needs an entry in the `provenance` object. The entry is written at the key (`"k"`), or at each
element of a list or dict (`"windows[0]"`, `"params.e_mature"`), or with a `*` for key names (`"reference_*"`). A profile
that leaves a parameter unlabelled does not pass `profile check`, and a test runs the check on every profile in
`examples/profiles`.

```json
{
  "id": "my_stage_mouse_v1",
  "description": "Defaults of my_stage for the mouse visual cortex.",
  "tau": 12.5,
  "gain": 1.0,
  "provenance": {
    "tau": {"status": "measured", "quantity": "membrane time constant of L2/3 pyramidal cells (ms)",
            "species": "mouse", "region": "V1 layer 2/3", "age": "adult",
            "source": ["doi:10.xxxx/yyyy"], "evidence": "full_text",
            "note": "Table 2, whole-cell recordings at 34 C"},
    "gain": {"status": "free", "quantity": "overall gain of the stage", "note": "no data; explore freely", "bounds": [0, 4]}
  }
}
```

### Species and regions as layers

A profile may `extends` another one (an id, a file name or a path). Its keys are merged over the parent's (dicts merge,
lists and scalars are replaced) and its `provenance` entries over the parent's. A key the child sets needs its own entry:
the parent's entry would describe another species.

```json
{"id": "my_stage_rat_v1", "extends": "my_stage_mouse_v1", "tau": 18.0,
 "provenance": {"tau": {"status": "measured", "quantity": "membrane time constant of L2/3 pyramidal cells (ms)",
                        "species": "rat", "source": ["doi:10.xxxx/zzzz"], "evidence": "abstract"}}}
```

Load a resolved profile with `neurogenesis.profiles.load_parameter_profile("my_stage_rat_v1")`. `provenance_counts` tells
how much of a set of profiles is measured, derived, assumed or free.

### Free parameters and ranges of validity

`free` entries are virtual parameters. A stage accepts any value of a scalar parameter inside the range declared in
`neurogenesis/stages/bounds.py` (`PARAM_BOUNDS`), whatever the other parameters are, and `tests/test_stage_bounds.py` runs
each listed stage on random values and on the corners of that box. These are ranges of validity of the code, not of
biology: a gene or a `Fit` may take any value inside them, including combinations found in no species. A profile default
must lie inside them (also tested). When you add a numeric parameter that users will draw from a gene, add it to
`PARAM_BOUNDS` and to the harness of that test.

### What a project rests on

A project can list the profiles whose defaults it started from in `metadata["profiles"]`. Then

```
python -m neurogenesis profile report project.json
```

prints how many of their entries are measured, derived, assumed or free, how much of the sources was actually read, which
entries are assumed or free, and how many parameters the project leaves to genes and to `Fit`
(`neurogenesis.profiles.project_report` gives the same as a dict, for a use-case page). It is a declaration, not a check:
the project's own values may differ from the profiles'. When a use case claims that a simulation recovers independent
observations, quote this report and keep the compared observations out of the parameters.

### Adding a profile (checklist)

1. One profile per pull request, in `examples/profiles/` (or a folder you point `$NEUROGENESIS_PROFILES` at).
2. Every parameter has a status, a `quantity`, and, if measured or derived, a DOI and what was read.
3. Say which species, region and age the number is from, and write the caveat in `note` when you apply a value to
   another region.
4. `python -m neurogenesis profile check <file> --online` passes.
5. Say in the pull request which values were checked in the full text and which only in the abstract.
6. Reference outputs of a profile (tables of expected counts, say) are `derived` from its parameters: do not present them
   as measurements.

Validation against observations: when a use case claims that a simulation recovers independent observations, the
observations it compares with must not have been used to fix its parameters (see the module validation rules in
contributing).


# Page: reference/neuron-models.md

## Neuron models and their cost

### Choosing a model
Every grown network runs with any registered model:
```python
from neurogenesis.spiking.batch import simulate_counts
counts = simulate_counts(nets, currents, ticks=300, model="hh")      # "lif_int" (default), "two_compartment", "hh"
```
All models share the same transmission layer: the same synapses, axonal delays (up to 255 ticks of 1 ms) and driver
neurons. The Poisson drivers and intrinsic bursters fire at exactly the same ticks in every model (tested). Models
differ in how a neuron integrates its inputs between spikes.

| Model | Level | Integration | Uses from the grown network | Known properties tested |
|---|---|---|---|---|
| `lif_int` | point neuron, integer | 1 update per tick | everything (leak and synaptic shifts, threshold, refractory period) | identical spikes in Python, JAX and the browser |
| `two_compartment` | soma + dendrite, intermediate | Euler, 4 substeps per tick | membrane time constant 2^shift_v ms, synaptic time constant 2^shift_s ms, threshold, refractory period | same rheobase as `lif_int`; maximum rate set by the refractory period; dendritic (synaptic) input attenuated relative to somatic input |
| `hh` | Hodgkin–Huxley point neuron (Na, K, leak) | exponential Euler for gates, 40 substeps of 0.025 ms | synaptic time constant; threshold and refractoriness come from the channels | rest near −65 mV; abrupt onset of repetitive firing (type II) near 6.3 µA/cm² from rest, at 50–60 Hz; about 65–70 Hz at 10 µA/cm² |

**Units.** External currents and synaptic weights keep the toolbox's integer units.
- **Hodgkin–Huxley:** 1 unit = 0.74 µA/cm², so its onset of repetitive firing falls at the integer neuron's rheobase (8.5 units).
- **Two compartments:** the conversion is set the same way.
- **Weights:** a synaptic weight w delivers the charge of a w-unit current lasting 1 ms.

This makes the models comparable on the same network, not quantitatively equivalent.

**Adding a model:** `neurogenesis.spiking.models.register_model(module)`. The module provides `name`, `description`,
`substeps`, `state_bytes`, `flops`, `init_state(net, seed)` and `tick(net, state, ext)`, and uses
`models.base.drivers` and `models.base.transmit`, so that drivers and delays behave as in every other model. Then add it
to the parametrised tests in `tests/test_models.py`.

### Knowing the cost before trying
```
python -m neurogenesis estimate --neurons 3e8 --synapses-per-neuron 5000 --seconds 1
```
For each model, the estimator gives:
- **memory**, counted array by array: neuron parameters and state, synapse arrays and their per-tick buffers, recorded counts;
- **the memory available** (free GPU memory if JAX runs on a GPU, else RAM);
- **the time** per simulated second.

**How the time is computed.** It comes from a throughput measured once per model and device: three short benchmarks,
fitting a fixed cost per tick, a cost per neuron-substep and a cost per synapse. The result is cached in
`~/.cache/neurogenesis/throughput.json`. The tests check that the estimate stays within a factor of 4 of the measured
time on a 3000-neuron, 120 000-synapse network.

**Guard rail.** Every batched simulation calls `cost.check` first:
- it refuses a network that does not fit in memory (`CostError`, unless `force=True`);
- it warns when a calibrated estimate exceeds one hour.

**Example on the development cluster CPU (2026-11-01; numbers depend on the machine):**

| Network | lif_int | two_compartment | hh |
|---|---|---|---|
| 10⁴ neurons, 10⁷ synapses | 250 MB; ~25 min / simulated s | 250 MB; ~47 min / simulated s | 250 MB; ~26 min / simulated s |
| 3 × 10⁸ neurons, 5000 synapses each (an order of magnitude for a primate ventral visual stream, illustrative only) | does not fit: 37.5 TB; ~7 years / simulated s | does not fit; ~13 years / s | does not fit; ~7 years / s |

On this CPU, synapses dominate both memory and time; the neuron model matters once neurons dominate. For 10⁶ neurons
without synapses, Hodgkin–Huxley costs about 50 times more than the integer neuron (40 substeps of a heavier update).
Large extrapolations are linear and ignore memory bandwidth and multi-device communication, so they are optimistic.


# Page: reference/bridge-protocol.md

## Bridge protocol, version 1.0

The desktop apps drive the toolbox through one server, which ships with the toolbox:

```bash
python -m neurogenesis.bridge
```

The server code is in `neurogenesis/bridge/`, and `tests/test_bridge.py` exercises every method. Run the server with
a Python that has the toolbox installed, or with its checkout first on `PYTHONPATH`.

### 1. Transport
- The client writes requests to the server's **stdin** and reads its messages on **stdout**.
- Every message is one UTF-8 JSON object on one line (`\n` terminated).
- The server's **stderr** carries free-form logs (library prints, tracebacks). Show them in a console, never parse them.
- The server first sends a `ready` event, after its heavy imports (a few seconds). Clients queue their requests until it arrives.
- The server stops when its stdin closes.

### 2. Messages
| Kind | Shape |
|---|---|
| request | `{"id": <int>, "method": "<name>", "params": {...}}`. `params` may be omitted. |
| result | `{"id": <same>, "result": <any JSON>}` |
| error | `{"id": <same>, "error": "<Type>: <message>", "kind": "error" \| "resource", "trace": "<python traceback>"}` |
| event | `{"event": "ready", "data": {"python": "3.12.4", "protocol": "1.0", "ngb": 1}}` or `{"event": "progress", "job": <request id>, "data": {...}}` |

- Ids are chosen by the client and must be unique among pending requests.
- Requests run one at a time, in order. The exception is long jobs (`evolve`), which run beside the others and send
  `progress` events tagged with their request id.
- `kind: "resource"` means the server refused the action before running it, because it would not fit in this
  machine's memory or temporary disk. The message says what it needs and what is free. Show it to the user (the Mac
  app uses an alert). Refusals have no `trace`.
- An unknown method returns an error. Clients must ignore unknown fields and events, so that minor versions stay
  compatible.

### 3. Versions
- **`protocol`.** The version is `MAJOR.MINOR`, in the `ready` event and in `capabilities`. MINOR adds methods or
  optional fields. MAJOR changes or removes them. A client checks MAJOR and warns on a mismatch.
- **`ngb`.** The NGB1 format version (section 6) is an integer.

### 4. Capability discovery
The server reports what the installed toolbox provides, so features merged later appear in the apps without changing
them.
- **`capabilities`** returns `{tasks: [{task, name, available}], python, executable, repo, platform, protocol, ngb}`.
  `task` is a roadmap id ("A4", "C2", …, or "core"). `repo` is where the toolbox is imported from.
- **`stages`** returns `{available, stages: [{name, task, available, doc, params: [{name, default}], has_cost}]}`. This
  is the roadmap's catalogue (C1–C13) merged with the registered stages. `available` at the top is true when the
  scheduler (A4) is present.
- **`models`** returns `{models: [{name, description, substeps, state_bytes, flops}]}`.
- **`rules`** returns `{phase: [rule names]}`.
- **`plasticity_rules`** returns `{available, rules: [{name, params: [{name, default}]}]}`.
- **`geometry_types`** returns `{types: [{type, doc, params: [{name, kind: "vec3" | "float" | "string" | "geometry", default}]}]}`.

### 5. Methods
Networks are passed by NGB1 file path (section 6). Results that contain a network have the form
`{path, neurons, synapses, morphology, regions?}`, where `regions` names the compartment ids of a grown body plan.
Files written by the server live in its temporary directory until it exits.

| Method | Params | Result |
|---|---|---|
| `ping` | – | `"pong"` |
| `capabilities`, `stages`, `models`, `rules`, `plasticity_rules`, `geometry_types` | – | section 4 |
| `resources` | – | `{available, physical, cores, disk_free, fraction}` (bytes) |
| `generate` | `morphology` (column \| layered \| microcircuit), `neurons`, `seed`, preset options | network |
| `load_network` | `path` (network JSON) | network |
| `save_network` | `ngb`, `path`, `morphology?` | `{path}` |
| `wire` | `ngb`, `seed`, `conduction_velocity` | network (rewired by distance) |
| `stats` | `ngb` | topology statistics |
| `plan_defaults` | `plan`, `genome` | `{genome}` completed with neutral genes for every region and projection |
| `estimate_grow` | `plan`, `genome` | `{neurons, synapses, bytes, disk, regions, fits, message, available}` |
| `grow` | `plan`, `genome`, `seed`, `projections?` | network |
| `grow_steps` | `plan`, `genome`, `seed` | `{steps: [network + label], source}`: placement, then one step per projection |
| `plan_load`, `plan_save` | `path` (and `plan`) | `{plan}` / `{path}` |
| `project_new`, `project_load`, `project_save`, `project_validate` | `path`, `project` | `{project}` / `{path}` / `{ok, message}` |
| `geometry_preview` | `geometry`, `n`, `seed` | `{points: [[x, y, z]], lo, hi}`. Empty shapes are refused. |
| `geometry_cells` | `geometry`, `n`, `seed`, `inh_fraction`, `role`, `compartment`, `refractory`, `weight` | network (unconnected) |
| `run_schedule` | `project`, `seed` | `{steps: [network + label], source}`: one checkpoint per stage repetition |
| `schedule_info` | `project`, `N`, `E` | `{genes: [text], gene_list: [{path, lo, hi, integer}], cost: {bytes, ops}}` |
| `simulate` | `ngb`, `model`, `ticks`, `ext: {"index": current}`, `seed` | `{ticks, t: [...], i: [...], model}`: spike times |
| `estimate` | `neurons`, `synapses`, `ticks`, `models?`, `calibrate?` | `{estimates: [{model, memory_bytes, fits, verdict, ...}]}` |
| `fi_curve` | `shift_v`, `shift_s`, `threshold`, `refractory`, `hi`, `step`, `ticks`, `model` | `{currents, rate_hz}` |
| `regularity` | `ngb`, `indices?` (at most 2048) | `{regularity, cells}` |
| `plasticity` | `ngb`, `rule`, `params`, `ticks`, `t`, `i` (recorded spikes) | `{weights: [per synapse], mean_change}` |
| `genes` | `genome` | `{genes: [{path, value}]}`: every number of the genome |
| `evolve` (long) | `plan`, `genome`, `genes: [{path, lo, hi}]`, `target_hz`, `gens`, `pop`, `seeds`, `ticks`, `input_current` | `{history, genome}`, plus a `progress` event per generation |
| `module_load` | `path`, `seed` | `{name, plan, genome, inputs, outputs, description, neuron_model, validation, network}` |
| `module_save` | `name`, `plan`, `genome`, `inputs`, `outputs`, `description`, `neuron_model`, `path` | `{path}` |
| `module_compose` | `paths`, `prefixes?`, `links: [[src, dst, genes?]]`, `offsets?`, `name`, `seed` | `{name, plan, genome, prefixes, network}` |
| `ui_schema` (planned, H3) | `project?` | the UI schema document (`UI_SCHEMA.md`); until it exists, clients use `ui_schema.example.json` |

**JSON conventions.**
- **Genomes** use `neurogenesis.genome.io`: a projection key is `"src->dst"`, and arrays are lists.
- **Schedules** are A4 entries, `{name, operator, params, when?, repeat}`, where each param is an A3 dict
  `{"type": "Const" | "Gene" | "Field" | "Fit" | "Expr", ...}`.
- **Gene paths** are lists of keys, `"src->dst"` projection keys and integer indices, for example
  `["guidance", "a->b", "sigma"]`.

### 6. NGB1 network format (version 1)
NGB1 is little endian, with no padding and no alignment requirement. Writer and reader: `neurogenesis/bridge/ngb.py`.

| Offset | Content |
|---|---|
| 0 | magic `"NGB1"` (4 bytes) |
| 4 | int32 N, the number of neurons |
| 8 | int32 E, the number of synapses |
| 12 | int32 flags: bit 0 set when an extension table follows (below); every other bit is reserved and must be 0 |
| 16 | positions: N × 3 float64 (x, y, z, row-major) |
| | directions: N × 3 float64 |
| | 10 int32 columns of N values each, in this order: mode, shift_v, shift_s, base_weight, burst_period, burst_duty, refractory, role, compartment, threshold |
| | 3 float64 columns of N values each: poisson_hz, conn_radius, conn_prob |
| | 4 int32 columns of E values each: pre, post, weight, delay |

- **Size.** Without extensions (flags 0), the file is exactly `16 + 112 N + 16 E` bytes. Readers reject any other
  size, and reject files with reserved flag bits set.
- **Indices.** Neuron indices (pre, post) are int32 everywhere, in files and in kernels: never 16-bit.
- **Delays.** A delay is stored as int32. Kernels of NGB1 version 1 deliver delays 0–31; larger ones never arrive,
  as in the Python reference. Longer delays will need more history words per neuron in the kernels, not a new format.
- **Field meanings.** They are those of `neurogenesis.spiking.network.NeuronData` and `SynapseData`. mode: 0 LIF,
  1 Poisson, 2 burster. role: 0 internal, 1 input, 2 output. compartment: −1 means none.
- **Exactness.** Float64 keeps positions and Poisson rates exactly as in Python, so a round trip through NGB1 is
  exact. A kernel that reads NGB1 must compute the Poisson threshold as
  `min(4294967295, trunc(poisson_hz / 1000 × 2³²))`, as `neurogenesis.spiking.reference` does.
- **Extension columns** (flags bit 0). After the synapse columns comes an int32 C, then C optional columns. Each
  column has a 32-byte header and then its data:

  | Header field | Content |
  |---|---|
  | name | 16 bytes of ASCII, NUL padded, e.g. `receptor`, `pre_node`, `post_node` |
  | axis | int32: 0 per neuron, 1 per synapse |
  | dtype | int32: 1 int8, 2 uint8, 3 int16, 4 int32, 5 int64, 6 float32, 7 float64 |
  | components | int32, at least 1 (e.g. 3 for a vector) |
  | reserved | int32, 0 |

  The data is (N or E) × components values of that dtype, little endian. Readers skip columns they do not know; the
  size of every column follows from its header. Optional columns never change the meaning of the mandatory ones:
  a network read without them is still the same network for every kernel.
- **Future changes.** New optional per-neuron or per-synapse data goes in extension columns, with no new version. A
  change to the mandatory layout gets a new magic (`NGB2`), and readers keep accepting NGB1.


# Page: reference/ui-schema.md

## UI schema, version 1.0

The UI schema is a JSON document that tells the desktop apps what the installed toolbox offers. It covers
developmental stages and their parameters, neuron models and their costs, development rules, the genes of a
schedule, and the Project schema version.

The apps build their editors from it. Nothing about an individual stage, Param, model, rule or gene is coded into an
app; app code is only layout, look and interaction. The macOS, Windows and Linux apps therefore show the same panels,
and a stage merged into the toolbox appears in all three without changing them.

| | |
|---|---|
| Format owner | desktop apps (this file). Changes need a PR that keeps all three apps working. |
| Exporter | `python -m neurogenesis ui-schema` (roadmap task H3), and the bridge method `ui_schema` |
| Machine-checkable form | `ui_schema.schema.json` (JSON Schema 2020-12) |
| Complete example | `ui_schema.example.json`; `tests/test_ui_schema.py` validates it against the schema |

### 1. Versioning
- `"format": "neurogenesis-ui-schema"` and `"version": "MAJOR.MINOR"`.
- A MINOR bump adds optional fields or new enum values. Apps ignore fields they do not know. Apps show an item with
  an unknown `type` or `cost` form as read-only JSON, never as an error.
- A MAJOR bump renames, removes or changes the meaning of a field. Apps refuse a MAJOR version they do not know and
  tell the user to update.

### 2. Document
```json
{
  "format": "neurogenesis-ui-schema",
  "version": "1.0",
  "generated_by": {"toolbox": "0.1.0", "python": "3.12.15", "date": "2026-11-01T18:00:00Z"},
  "project_schema_version": "1.1.0",
  "param_sources": [...],
  "stages": [...],
  "models": [...],
  "rule_phases": [...],
  "rules": [...],
  "schedule_genes": [...]
}
```
Every top-level key is required. A list may be empty: for example, `stages` before any stage is registered.

#### 2.1 `param_sources`: where a parameter's value comes from (A3)
Each entry has these fields:

| Field | Meaning |
|---|---|
| `type` | `"Const"`, `"Gene"`, `"Field"`, `"Fit"` or `"Expr"`. These are the `type` values of A3 param dicts. |
| `title` | Short label. |
| `doc` | One or two sentences. |
| `inputs` | The fields to edit for this source, each `{name, type, doc}`. For example, Gene has `path` (a gene path), `lo`, `hi` and `integer`. |

Apps render the source switch and its inputs from this list. When a source has an input of an unknown type, apps edit
that input as raw JSON.

#### 2.2 `stages`: registered stages (A4) and planned ones
Each stage has these fields:

| Field | Meaning |
|---|---|
| `operator` | Registry name, used in schedule entries (`{"operator": ...}`). |
| `title`, `description` | Label and help text. `description` is the first paragraph of the stage's docstring. |
| `task` | Roadmap id (`"C2"`), or `"plugin"` for a stage that comes from a plugin. |
| `available` | False for planned stages that are not registered yet. Apps show them but cannot run them. |
| `params` | A list of Param specs (2.3), in display order. |
| `cost` | `null` if the stage declares no cost. Otherwise `{"declared": true, "samples": [{"N", "E", "bytes", "ops"}]}`: the stage's `cost(N, E, defaults)` evaluated at the exporter's reference sizes, so apps can show how the stage scales. Exact costs for a real schedule come from the bridge (`schedule_info`). |
| `repeatable` | The stage accepts `repeat` greater than 1. |
| `conditional` | The stage accepts a `when` condition. |

#### 2.3 Param spec
| Field | Required | Meaning |
|---|---|---|
| `name` | yes | Key in the schedule entry's `params`. |
| `title` | yes | Label. |
| `doc` | yes | Help text, shown by the (?) button: what the parameter is and what it changes. |
| `type` | yes | The value's type: `float`, `int`, `bool`, `string`, `choice`, `vec3`, `geometry` (name of a project geometry), `field` (name of a project field), `gene_path`, `json`. |
| `default` | yes | An A3 param dict, e.g. `{"type": "Const", "value": 0.1}`, as a new schedule entry stores it. |
| `sources` | yes | Allowed `param_sources` types, e.g. `["Const", "Gene", "Field"]`. |
| `units` | no | E.g. `"per tick"`, `"space units"`, `"ms"`. Empty or absent means dimensionless. |
| `min`, `max` | no | Bounds of the value (inclusive). They also bound Gene `lo` / `hi` and Fit `init`. |
| `step` | no | Increment for steppers and sliders. |
| `choices` | for `choice` | `[{"value", "title"}]` |
| `per_cell` | no | True if the parameter can vary across cells (sources `Field` and `Expr` resolve per cell). |
| `advanced` | no | Shown collapsed by default. |

**Widgets by type.** The apps choose widgets from the type, never from the name:

| `type` | Widget |
|---|---|
| `float`, `int` | Number field. A slider is added when both bounds are finite. |
| `bool` | Toggle. |
| `string` | Text field. |
| `choice` | Picker. |
| `vec3` | Three number fields, x / y / z. |
| `geometry` | Picker over the project's geometry names. |
| `field` | Picker over the project's field names. |
| `gene_path` | Gene path editor. |
| `json` | JSON editor. |
| anything else | Read-only JSON. |

Every widget has a label (`title`) and a (?) help (`doc`, with `units` and the bounds).

#### 2.4 `models`: neuron models and their costs
Each model has these fields:

| Field | Meaning |
|---|---|
| `name`, `title`, `description` | Identity and help text. |
| `substeps` | Updates per 1 ms tick. |
| `state_bytes` | Per neuron. |
| `flops` | Per neuron and substep. |
| `cost` | `{"bytes_per_neuron", "bytes_per_synapse", "seconds_per_neuron_substep", "seconds_per_synapse_tick", "seconds_per_tick_overhead", "device"}`. The `seconds_*` fields and `device` come from `neurogenesis.cost` calibration and are `null` when this machine is not calibrated. |
| `native` | True when the apps can run the model live (`lif_int`, through `nglif`); otherwise it runs through the bridge's `simulate`. |

#### 2.5 `rule_phases` and `rules`: development rules (`neurogenesis.development.rules`)
- `rule_phases` lists every phase, in order: `[{"name": "placement", "title", "doc"}, ...]`.
- `rules` lists every rule, as `[{"phase", "name", "doc", "genes": [gene path patterns that activate it], "default": bool}]`.
  `default` is true when the rule is in the default plan rules.

#### 2.6 `schedule_genes`: the genes a schedule uses (A3 `collect_genes`)
Each gene is `{"path", "lo", "hi", "integer", "used_by": [{"stage", "param"}]}`. Its path is a gene path (2.7).

The list is empty in a document exported without a project. With a project (`ui-schema --project p.json`, or the
bridge method `ui_schema` with `project`), it lists the genes of that project's schedule. The Evolution panel offers
exactly these genes.

#### 2.7 Gene paths
A gene path is a JSON list of keys. `"src->dst"` stands for a projection key (a tuple in Python), and integers index
arrays. For example, `["guidance", "sensor->map", "B", 0, 1]`. The same convention is used in PROTOCOL.md.

### 3. Obtaining it
- `python -m neurogenesis ui-schema [--project p.json] [--calibrated]` writes the document to stdout (H3).
- Bridge method `ui_schema`, with params `{project?}`, returns the same document. Apps call it after `ready`, and
  again after the user refreshes the toolbox.
- Until H3 is merged, apps may render `ui_schema.example.json` to develop and test their panels. They mark
  `available: false` stages as not runnable.


# Page: reference/glossary.md

## Glossary

Audit
: Re-evaluating a chosen genome on development seeds never used during the search, including knockouts.

Body plan
: Regions (boxes with placement rules, roles and signs) and the projections allowed between them.

Bridge
: The JSON-lines server (`python -m neurogenesis.bridge`) through which apps and other programs use the toolbox.

Capacity test
: Checking with a hand-set genome that an architecture can do a task at all, before any genetic search.

Development seed
: The random seed of growth. Same plan + genome + seed → the same network.

Driver neuron
: A neuron that fires on its own: Poisson (`mode` 1) or burster (`mode` 2).

Field
: Values defined on space (gradients, layers, maps), sampled at cell positions.

Fingerprint
: A hash of a grown network, frozen in tests to detect any change in development.

Gene
: A path into the genome plus bounds; coded in [0, 1] for evolution.

Island
: One independent evolving population; islands use disjoint development seeds.

Module
: Plan + genome + interface + validation + provenance + neuron model, saved as JSON and reusable.

NGB1
: The binary network format shared by the bridge, the C kernel and the apps.

nglif
: The portable C implementation of the integer LIF kernel, used by the desktop apps.

Param
: The source of a stage parameter: `Const`, `Gene`, `Field`, `Fit` or `Expr`.

Rule
: A small function modifying one phase of the fixed growth procedure; inactive without its genes.

Schedule
: Ordered list of stages (each with Params, `repeat`, `when`) that transforms a Tissue.

Stage
: A registered operator `Tissue × Params × rng → Tissue`.

Tick
: One simulation step, 1 ms.

Tissue
: The struct of arrays (cells and connectivity) that stages transform.


# Page: background/design-principles.md

## Design principles

1. **General.** No species, organ, task or environment inside the toolbox. Projects bring their own regions, stimuli
   and objectives; species-specific data (transmitter profiles, datasets) are data files outside the core.
2. **Every parameter has a declared source.** By hand, from a field, from genes, fitted to data, or a formula — the
   same stage works in supervised, unsupervised and semi-supervised workflows.
3. **Deterministic.** Seeds in, identical networks and integer spikes out. Rules draw no random numbers; each stage
   repetition has its own random stream.
4. **Backward compatible.** New options are off by default; existing plans and genomes grow the same networks
   (checked by fingerprints).
5. **Low compute.** Linear in cells and synapses, local interactions through a shared cell list, integer kernels,
   struct-of-arrays. Every stage and model declares its cost, and the toolbox refuses what cannot fit before running.
6. **One kernel, several implementations, identical spikes.** Python, JAX, browser and C; any change to one is made to
   all, with equivalence tests.
7. **Robust to development.** Genomes are selected for working across development seeds, not for one network.
8. **Honest claims.** Results are statements about simulations; biological mechanisms are cited, and fidelity is not
   claimed beyond what is implemented and tested.
9. **Shareable.** Circuits are modules; extensions are plugins; apps learn the toolbox's stages and Params at run time.


# Page: background/literature.md

## Related work and literature

neurogenesis-sim sits between four lines of work: models of neural development, developmental (indirect) encodings
in neuroevolution, spiking network simulators, and connectome-constrained modelling. This page lists the work the
toolbox builds on or should be compared with.

> **Note on references.** This list was compiled with the help of an AI assistant. References with a DOI were checked against Crossref, DataCite or Europe PMC on 2026-11-02; the others come from well-known publications. Check each reference against the publisher's record before citing it.

### How the toolbox relates to existing tools

| Tool | Focus | Difference from neurogenesis-sim |
|---|---|---|
| Cx3D / BioDynaMo | physics of growing cells and neurites, agent-based | detailed mechanics; no evolution of genomes for function, no spiking kernel tied to development |
| NETMORPH, TREES toolbox | statistical or optimal-wiring growth of neuronal morphologies | morphology-centred; networks are not grown from evolvable genomes for a task |
| NeuroDevSim | phenomenological growth, migration and pruning of neurons in a tissue (Python, parallel) | the closest Python peer for growth stages; no evolvable genome, no integrated spiking kernel |
| DevNCA | neural cellular automata co-evolving developmental patterns and plasticity rules for spiking networks | the closest conceptual neighbour (development + spiking + evolution); no biological geometry, cell types or data interchange |
| NEST, Brian 2, NEURON, GeNN, Arbor | simulating spiking networks | networks are specified, not developed; neurogenesis-sim exports to them (SONATA now; PyNN and NeuroML 2 planned, roadmap phase J) |
| SONATA, NeuroML 2, PyNN, NIR | describing networks independently of a simulator | interchange formats, not development; targets of the exporters |
| Connectome-Manipulator | rewiring, fitting and transplanting connectomes of SONATA circuits | operates on given circuits; can serve as a baseline for, and manipulate, grown circuits written as SONATA |
| NEAT, HyperNEAT, ES-HyperNEAT, NDPs | evolving networks through (indirect) encodings | abstract encodings; no biological geometry, cell types, stages or spiking biophysics |
| Connectome-constrained models | simulating measured wiring diagrams | wiring taken as given; neurogenesis-sim asks how development could produce its statistics |

### Models of neural development
- Turing AM (1952). The chemical basis of morphogenesis. *Philosophical Transactions of the Royal Society B* 237:37–72.
- Wolpert L (1969). Positional information and the spatial pattern of cellular differentiation. *Journal of
  Theoretical Biology* 25:1–47.
- Gierer A, Meinhardt H (1972). A theory of biological pattern formation. *Kybernetik* 12:30–39.
- Sperry RW (1963). Chemoaffinity in the orderly growth of nerve fiber patterns and connections. *PNAS* 50:703–710.
- Willshaw DJ, von der Malsburg C (1976). How patterned neural connections can be set up by self-organization.
  *Proceedings of the Royal Society B* 194:431–445.
- Swindale NV (1996). The development of topography in the visual cortex: a review of models. *Network* 7:161–247.
- Collier JR, Monk NAM, Maini PK, Lewis JH (1996). Pattern formation by lateral inhibition with feedback: a
  mathematical model of Delta–Notch intercellular signalling. *Journal of Theoretical Biology* 183:429–446.
- McLaughlin T, O'Leary DDM (2005). Molecular gradients and development of retinotopic maps. *Annual Review of
  Neuroscience* 28:327–355.
- Simpson HD, Mortimer D, Goodhill GJ (2009). Theoretical models of neural circuit development. *Current Topics in
  Developmental Biology* 87:1–51.
- van Ooyen A (2011). Using theoretical models to analyse neural development. *Nature Reviews Neuroscience*
  12:311–326.
- Goodhill GJ (2016). Can molecular gradients wire the brain? *Trends in Neurosciences* 39:202–211.

### Developmental and growth simulators
- Zubler F, Douglas R (2009). A framework for modeling the growth and development of neurons and networks.
  *Frontiers in Computational Neuroscience* 3:25. (Cx3D)
- Koene RA, Tijms B, van Hees P, et al. (2009). NETMORPH: a framework for the stochastic generation of large scale
  neuronal networks with realistic neuron morphologies. *Neuroinformatics* 7:195–210.
- Cuntz H, Forstner F, Borst A, Häusser M (2010). One rule to grow them all: a general theory of neuronal branching
  and its practical application. *PLoS Computational Biology* 6:e1000877. (TREES toolbox)
- Breitwieser L, Hesam A, de Montigny J, et al. (2022). BioDynaMo: a modular platform for high-performance
  agent-based simulation. *Bioinformatics* 38:453–460.
- De Schutter E (2023). Efficient simulation of neural development using shared memory parallelization. *Frontiers
  in Neuroinformatics* 17:1212384. doi:10.3389/fninf.2023.1212384 (NeuroDevSim)

### Activity-dependent development
- Wong ROL (1999). Retinal waves and visual system development. *Annual Review of Neuroscience* 22:29–47.
- Ben-Ari Y (2002). Excitatory actions of GABA during development: the nature of the nurture. *Nature Reviews
  Neuroscience* 3:728–739.
- Khazipov R, Sirota A, Leinekugel X, et al. (2004). Early motor activity drives spindle bursts in the developing
  somatosensory cortex. *Nature* 432:758–761.
- Bi GQ, Poo MM (1998). Synaptic modifications in cultured hippocampal neurons: dependence on spike timing, synaptic
  strength, and postsynaptic cell type. *Journal of Neuroscience* 18:10464–10472.
- Frémaux N, Gerstner W (2016). Neuromodulated spike-timing-dependent plasticity, and theory of three-factor learning
  rules. *Frontiers in Neural Circuits* 9:85.

### Developmental encodings and neuroevolution
- Kitano H (1990). Designing neural networks using genetic algorithms with graph generation system. *Complex Systems*
  4:461–476.
- Gruau F (1994). *Neural network synthesis using cellular encoding and the genetic algorithm.* PhD thesis, École
  Normale Supérieure de Lyon.
- Whitley D, Rana S, Heckendorn RB (1999). The island model genetic algorithm: on separability, population size and
  convergence. *Journal of Computing and Information Technology* 7:33–47.
- Stanley KO, Miikkulainen R (2002). Evolving neural networks through augmenting topologies. *Evolutionary
  Computation* 10:99–127.
- Stanley KO (2007). Compositional pattern producing networks: a novel abstraction of development. *Genetic
  Programming and Evolvable Machines* 8:131–162.
- Stanley KO, D'Ambrosio DB, Gauci J (2009). A hypercube-based encoding for evolving large-scale neural networks.
  *Artificial Life* 15:185–212.
- Risi S, Stanley KO (2012). An enhanced hypercube-based encoding for evolving the placement, density, and
  connectivity of neurons. *Artificial Life* 18:331–363.
- Zador AM (2019). A critique of pure learning and what artificial neural networks can learn from animal brains.
  *Nature Communications* 10:3770.
- Najarro E, Sudhakaran S, Risi S (2023). Towards self-assembling artificial neural networks through neural
  developmental programs. *Proceedings of the Artificial Life Conference (ALIFE 2023)*. doi:10.1162/isal_a_00697
- Gaskin B, et al. (2025). DevNCA: co-evolving developmental patterns and plasticity rules for self-organising spiking
  neural networks. *Proceedings of the Artificial Life Conference (ALIFE 2025)*. doi:10.1162/ISAL.a.840 (full author
  list not verified)
- Shuvaev S, Lachi D, Koulakov A, Zador A (2024). Encoding innate ability through a genomic bottleneck. *PNAS*
  121:e2409160121.

### Neuron models and spiking simulators
- Lapicque L (1907). Recherches quantitatives sur l'excitation électrique des nerfs traitée comme une polarisation.
  *Journal de Physiologie et de Pathologie Générale* 9:620–635.
- Hodgkin AL, Huxley AF (1952). A quantitative description of membrane current and its application to conduction and
  excitation in nerve. *Journal of Physiology* 117:500–544.
- Izhikevich EM (2003). Simple model of spiking neurons. *IEEE Transactions on Neural Networks* 14:1569–1572.
- Gerstner W, Kistler WM, Naud R, Paninski L (2014). *Neuronal Dynamics.* Cambridge University Press.
- Hines ML, Carnevale NT (1997). The NEURON simulation environment. *Neural Computation* 9:1179–1209.
- Gewaltig MO, Diesmann M (2007). NEST (NEural Simulation Tool). *Scholarpedia* 2:1430.
- Davison AP, Brüderle D, Eppler J, et al. (2009). PyNN: a common interface for neuronal network simulators.
  *Frontiers in Neuroinformatics* 2:11. doi:10.3389/neuro.11.011.2008 (online 2008)
- Gleeson P, Crook S, Cannon RC, et al. (2010). NeuroML: a language for describing data driven models of neurons and
  networks with a high degree of biological detail. *PLoS Computational Biology* 6:e1000815.
- Yavuz E, Turner J, Nowotny T (2016). GeNN: a code generation framework for accelerated brain simulations.
  *Scientific Reports* 6:18854.
- Stimberg M, Brette R, Goodman DFM (2019). Brian 2, an intuitive and efficient neural simulator. *eLife* 8:e47314.
- Dai K, Hernando J, Billeh YN, et al. (2020). The SONATA data format for efficient description of large-scale
  network models. *PLoS Computational Biology* 16:e1007696.
- Eshraghian JK, Ward M, Neftci EO, et al. (2023). Training spiking neural networks using lessons from deep learning.
  *Proceedings of the IEEE* 111:1016–1054.

### Connectomes and connectome-constrained models
- Peters A, Feldman ML (1976). The projection of the lateral geniculate nucleus to area 17 of the rat cerebral
  cortex. I. General description. *Journal of Neurocytology* 5:63–84. (origin of "Peters' rule")
- Hill SL, Wang Y, Riachi I, Schürmann F, Markram H (2012). Statistical connectivity provides a sufficient foundation
  for specific functional connectivity in neocortical neural microcircuits. *PNAS* 109:E2885–E2894.
- Markram H, Muller E, Ramaswamy S, et al. (2015). Reconstruction and simulation of neocortical microcircuitry.
  *Cell* 163:456–492.
- Billeh YN, Cai B, Gratiy SL, et al. (2020). Systematic integration of structural and functional data into
  multi-scale models of mouse primary visual cortex. *Neuron* 106:388–403.
- Scheffer LK, Xu CS, Januszewski M, et al. (2020). A connectome and analysis of the adult *Drosophila* central
  brain. *eLife* 9:e57443.
- Dorkenwald S, Matsliah A, Sterling AR, et al. (2024). Neuronal wiring diagram of an adult brain. *Nature*
  634:124–138.
- Schlegel P, Yin Y, Bates AS, et al. (2024). Whole-brain annotation and multi-connectome cell typing of *Drosophila*.
  *Nature* 634:139–152.
- Lappalainen JK, Tschopp FD, Prakhya S, et al. (2024). Connectome-constrained networks predict neural activity across
  the fly visual system. *Nature* 634:1132–1140.
- Shiu PK, Sterne GR, Spiller N, et al. (2024). A *Drosophila* computational brain model reveals sensorimotor
  processing. *Nature* 634:210–219.
- Berg S, Beckett IR, Costa M, Schlegel P, Januszewski M, Marin EC, et al. (2026). Sexual dimorphism in the complete
  *Drosophila* male central nervous system connectome. *Cell* 189:5504–5526.e15. doi:10.1016/j.cell.2026.08.015
  (FlyEM male CNS v1.0; preprint bioRxiv 10.1101/2025.10.09.680999). Data under CC-BY 4.0.
- Matsliah A, Yu S-c, Kruk K, et al. (2024). Neuronal parts list and wiring diagram for a visual system. *Nature*
  634:166–180. doi:10.1038/s41586-024-07981-1
- Nern A, Loesche F, Takemura S-y, et al. (2025). Connectome-driven neural inventory of a complete visual system.
  *Nature* 641:1225–1237. doi:10.1038/s41586-025-08746-0
- The MICrONS Consortium, Bae JA, Baptiste M, et al. (2025). Functional connectomics spanning multiple areas of mouse
  visual cortex. *Nature* 640:435–447. doi:10.1038/s41586-025-08790-w
- Reimann MW, Gevaert M, Shi Y, Lu H, Markram H, Muller E (2019). A null model of the mouse whole-neocortex
  micro-connectome. *Nature Communications* 10:3903. doi:10.1038/s41467-019-11630-x (statistical connectivity rules
  instantiated into a SONATA circuit)
- Pokorny C, Awile O, Isbister JB, Kurban K, Wolf M, Reimann MW (2025). A connectome manipulation framework for the
  systematic and reproducible study of structure–function relationships through simulations. *Network Neuroscience*
  9:207–236. doi:10.1162/netn_a_00429 (Connectome-Manipulator)

### Interchange formats, data standards and tools
Formats and libraries that neurogenesis-sim reads or writes, or plans to (roadmap phase J).
- Dai K, Hernando J, Billeh YN, Gratiy SL, Planas J, Davison AP, et al. (2020). The SONATA data format for efficient
  description of large-scale network models. *PLoS Computational Biology* 16:e1007696. doi:10.1371/journal.pcbi.1007696
- Dai K, Gratiy SL, Billeh YN, Xu R, Cai B, Cain N, et al. (2020). Brain Modeling ToolKit: an open source software
  suite for multiscale modeling of brain circuits. *PLoS Computational Biology* 16:e1008386.
  doi:10.1371/journal.pcbi.1008386 (BMTK)
- Pedersen JE, Abreu S, Jobst M, Lenz G, Fra V, Bauer FC, et al. (2024). Neuromorphic intermediate representation: a
  unified instruction set for interoperable brain-inspired computing. *Nature Communications* 15:8122.
  doi:10.1038/s41467-024-52259-9 (NIR)
- Cannon RC, Gleeson P, Crook S, Ganapathy G, Marin B, Piasini E, et al. (2014). LEMS: a language for expressing
  complex biological models in concise and hierarchical form and its use in underpinning NeuroML 2. *Frontiers in
  Neuroinformatics* 8:79. doi:10.3389/fninf.2014.00079
- Vella M, Cannon RC, Crook S, Davison AP, Ganapathy G, Robinson HPC, et al. (2014). libNeuroML and PyLEMS: using
  Python to combine procedural and declarative modeling approaches in computational neuroscience. *Frontiers in
  Neuroinformatics* 8:38. doi:10.3389/fninf.2014.00038
- Sinha A, Gleeson P, Marin B, Dura-Bernal S, Panagiotou S, Crook S, et al. (2025). The NeuroML ecosystem for
  standardized multi-scale modeling in neuroscience. *eLife* 13:RP95135. doi:10.7554/eLife.95135
- Gleeson P, Cantarelli M, Marin B, Quintana A, Earnshaw M, Sadeh S, et al. (2019). Open Source Brain: a collaborative
  resource for visualizing, analyzing, simulating, and developing standardized models of neurons and circuits.
  *Neuron* 103:395–411.e5. doi:10.1016/j.neuron.2019.05.019
- Abi Akar N, Cumming B, Karakasis V, et al. (2019). Arbor — a morphologically-detailed neural network simulation
  library for contemporary high-performance computing architectures. *27th Euromicro PDP*, 274–282.
  doi:10.1109/EMPDP.2019.8671560
- Rhodes O, Bogdan PA, Brenninkmeijer C, et al. (2018). sPyNNaker: a software package for running PyNN simulations on
  SpiNNaker. *Frontiers in Neuroscience* 12:816. doi:10.3389/fnins.2018.00816
- Pehle C-G, Pedersen JE (2021). Norse — a deep learning library for spiking neural networks. Zenodo.
  doi:10.5281/zenodo.4422025
- Cannon RC, Turner DA, Pyapali GK, Wheal HV (1998). An on-line archive of reconstructed hippocampal neurons. *Journal
  of Neuroscience Methods* 84:49–54. doi:10.1016/S0165-0270(98)00091-0 (origin of the SWC format)
- Thompson CL, Ng L, Menon V, Martinez S, Lee C-K, Glattfelder K, et al. (2014). A high-resolution spatiotemporal
  atlas of gene expression of the developing mouse brain. *Neuron* 83:309–323. doi:10.1016/j.neuron.2014.05.033
  (Allen Developing Mouse Brain Atlas; research and non-commercial use)
- Rübel O, Tritt A, Ly R, Dichter BK, Ghosh S, Niu L, et al. (2022). The Neurodata Without Borders ecosystem for
  neurophysiological data science. *eLife* 11:e78362. doi:10.7554/eLife.78362
- Garcia S, Guarino D, Jaillet F, Jennings T, Pröpper R, Rautenberg PL, et al. (2014). Neo: an object model for
  handling electrophysiology data in multiple formats. *Frontiers in Neuroinformatics* 8:10.
  doi:10.3389/fninf.2014.00010
- Smith AM, Niemeyer KE, Katz DS, Barba LA, Githinji G, Gymrek M, et al. (2018). Journal of Open Source Software
  (JOSS): design and first-year review. *PeerJ Computer Science* 4:e147. doi:10.7717/peerj-cs.147

Licences: these tools keep their own licences (several are GPL, LGPL or CeCILL, and the Allen data are
non-commercial). neurogenesis-sim uses them only as optional dependencies or as test references, never copies their
code, and never redistributes their data; see third party.

> **PLACEHOLDER.** A comparison table with measured numbers (time to grow and simulate a reference circuit) against Cx3D / BioDynaMo, Brian 2 and NEST; a BibTeX file of all references; and short summaries of how each listed model maps onto toolbox stages and Params.


# Page: background/developmental-questions.md

## Developmental Neuroscience Questions for Circuit Modeling

Use these foundational neuroscience questions to query textbooks, review papers, or literature notebooks (e.g., via NotebookLM or PubMed) to determine the exact biological rules and mathematical abstractions to prioritize in a developmental neural simulator.

---

### 1. Progenitor Division, Temporal Identity & Mitosis

1. **Symmetric vs. Asymmetric Division Transitions**
   - How do neural stem cells and radial glial progenitors transition from symmetric proliferative divisions (amplifying progenitor pool) to asymmetric neurogenic divisions (generating one self-renewing progenitor and one differentiated neuron or intermediate progenitor)?
   - What molecular or environmental signals determine the switch between proliferative and neurogenic modes?

   > **Literature Summary / Findings:**
   > - **Spindle Orientation & Cleavage Plane:** In the ventricular zone (VZ), early symmetric proliferative divisions feature a vertical cleavage plane (spindle parallel to ventricular surface), dividing the apical membrane patch and junctional complexes equally so both daughters remain as ventricular radial glia. Neurogenic asymmetric divisions switch to horizontal or oblique cleavage planes, causing only one daughter to retain apical contact/polarity determinants, while the other detaches and migrates basally (as an intermediate progenitor or nascent post-mitotic neuron).
   > - **Polarized Notch–Numb Circuit:** Asymmetric divisions partition the Notch antagonist Numb unequally. The daughter receiving high Numb exhibits Notch repression, upregulating proneural bHLH transcription factors (Ngn1/2, NeuroD) and exiting the cell cycle. The sister cell retains active Notch (forming NotchICD → HES repressors) and apical PAR complex (mPar3/mPar6/aPKC), preserving the self-renewing progenitor state.
   > - **Cell Cycle Kinetics ($G_1$ Lengthening):** Across neurogenesis, overall cell cycle time ($T_C$) and specifically $G_1$ phase length ($T_{G1}$) progressively lengthen. Progenitor maintenance ($P$ fraction) vs. cell-cycle exit ($Q$ fraction) depends on the balance of cell-cycle promoters (Cdk4/6, Cyclin D/E) vs. inhibitors ($\text{p27}^{Kip1}$, $\text{p21}^{Cip1}$). A surge in p27 and decline in Cyclin E drives an increasing fraction ($Q$) to exit mitosis into differentiation.
   > - **Extrinsic Niche Mitogens:** High early concentrations of FGF-2 and EGF sustain short $T_{G1}$ and suppress exit ($Q \to 0$), maintaining symmetric proliferation. Progressive mitogen decay permits $T_{G1}$ lengthening and allows intrinsic proneural and asymmetric neurogenic cascades to take over.

2. **Temporal Competence & Birth-Order Identity**
   - How do neural progenitors change their competence over time to generate different neuronal subtypes in a deterministic sequence (such as cortical deep layers before upper layers, or retinal ganglion cells before photoreceptors)?
   - Is temporal competence driven primarily by cell-intrinsic transcriptional clocks (e.g., sequential transcription factor cascades), extrinsic feedback cues from earlier-born neurons, or a fixed count of cell divisions?

   > **Literature Summary / Findings:**
   > - **Unidirectional Competence Restriction:** Progenitors undergo an irreversible, unidirectional narrowing of competence: early progenitors are broadly multipotent (can generate deep, intermediate, and superficial subtypes), whereas late progenitors permanently lose the ability to generate early/deep subtypes. Postmitotic migratory neurons are irreversibly fate-committed at birth.
   > - **Cell-Intrinsic Clocks:** Isolated clonal cultures confirm that temporal cascades operate autonomously in single progenitors without tissue-level feedback (e.g., Drosophila *Hunchback → Krüppel → PDM-1 → Castor*; vertebrate mammalian cascades).
   > - **Cell Cycle & Division Dependency:** Competence progression is not a strict integer-counting mechanism. While cytokinesis can act as a permissive gate for initial state transitions, subsequent competence states continue to advance on schedule even when divisions are blocked. Instead, $G_1$ lengthening ($T_{G1}$ quadrupling over integer cycles) expands the temporal window at the restriction point, enabling the gradual accumulation of downstream master transcription factors and proneural bHLH cascades.
   > - **Extrinsic Calibrating Feedback & Heterochronic Asymmetry:** Environmental cues can modulate velocity: heterochronic transplants show early progenitors in an older niche can "fast-forward" to superficial fates, but older progenitors in a younger niche cannot revert to deep fates. Paracrine signals from earlier-born cohorts (e.g., non-synaptic depolarizing GABA from migrating interneurons via $\text{GABA}_A$ receptors) provide negative feedback, slowing proliferation and tuning differentiation rates.

3. **Cleavage Plane & Daughter Cell Spacing**
   - Does the physical orientation of the mitotic spindle and cleavage plane (vertical vs. horizontal / apical vs. basal) influence the fate or subsequent spatial displacement of daughter cells?
   - How far are daughter cells physically displaced immediately following cytokinesis?

   > **Literature Summary / Findings:**
   > - **Spindle Orientation Dictates Fate & Trajectory:**
   >   - *Vertical Cleavage (perpendicular to ventricular surface):* Bisects the apical membrane patch (PAR complex) and divides Notch-1 and Numb symmetrically. Both daughters retain apical contact and adherens junctions at the ventricular surface, remaining in the VZ as proliferating radial glia.
   >   - *Horizontal/Oblique Cleavage (parallel/tilted relative to ventricular surface):* Asymmetrically partitions the apical membrane patch and segregates Numb preferentially into the basal daughter, while Notch-1 signaling remains unopposed in the apical daughter. The apical daughter remains at the ventricular margin in the cell cycle; the basal daughter detaches from the ventricular edge, exits the cell cycle, and begins active outward displacement toward the mantle zone / cortical plate.
   > - **Immediate Post-Cytokinesis Spacing:** Mitosis occurs with the progenitor rounded directly at the ventricular margin; cytokinesis deposits the two daughter cells immediately adjacent to one another (minimal steric separation, contact distance).
   > - **Subsequent Spatial Trajectories:**
   >   - Progenitors undergo *interkinetic nuclear migration* within the pseudostratified epithelium: elongating and ascending outward during $G_1$ into outer VZ for $S$ phase, then descending back to the apical surface during $G_2$ for mitosis.
   >   - Committed neuroblasts permanently detach from the apical edge and undergo radial migration (locomotion along radial glial guides or somal translocation), traveling hundreds to thousands of microns to their target laminar destinations.

---

### 2. Neuronal Migration, Laminar Organization & Cell Sorting

4. **Mechanisms of Inside-Out Cortical Layering**
   - How do later-born excitatory projection neurons navigate past earlier-born, deep-layer neurons to form an "inside-out" laminar gradient?
   - What are the relative contributions of radial glial scaffolding, chemoattractive cues, and detachment/stop signals (such as Reelin from Cajal-Retzius cells in the marginal zone) in terminating migration?

   > **Literature Summary / Findings:**
   > - **Radial Scaffolding & Bypassing Mechanisms:**
   >   - Preplate splitting: The earliest postmitotic wave forms the preplate (PP). Subsequent cortical plate (CP) neurons split this into the superficial marginal zone (MZ / Layer I with Cajal–Retzius cells) and the deep subplate (SP).
   >   - Successive waves (Layer VI $\to$ V $\to$ IV $\to$ III/II) crawl along common elongated radial glial fibers using saltatory *glial-guided locomotion* (dependent on astrotactin / Astn1 adhesion), followed by terminal *somal translocation* upon reaching beneath the marginal zone.
   >   - Young neuroblasts actively crawl *through* the intermediate zone and *past* older, already-settled deep-layer neurons, taking up residence immediately beneath the marginal zone.
   > - **Chemoattraction & Directionality:** Contact guidance along radial glia provides directional bias, supported by superficial chemoattractive cues (e.g. meningeal CXCL12 patterning Cajal–Retzius cells).
   > - **Reelin as a Glial-Detachment / Stop Signal:**
   >   - Cajal–Retzius cells in the MZ continuously secrete the large extracellular glycoprotein **Reelin**.
   >   - As migrating neuroblasts reach the top of the cortical plate beneath the MZ, Reelin signaling instructs them to disengage from the radial glial fiber and terminate forward locomotion.
   >   - **The "Log-Jam" Phenomenon (*reeler* phenotype):** Without functional Reelin, migrating neurons fail to detach from radial glial tracks upon reaching the top of the plate. Because successive waves share the same radial fibers, un-detached older neurons physically block subsequent waves ("log-jam"), causing later-born neurons to pile up in deeper layers and resulting in inverted/disorganized cortical lamination.

5. **Radial vs. Tangential Migration Interactions**
   - How do tangentially migrating inhibitory interneurons (originating from subcortical progenitor zones such as the ganglionic eminences) determine where to stop and integrate into specific cortical layers?
   - What mechanisms govern their transition from tangential wandering to radial insertion?

   > **Literature Summary / Findings:**
   > - **Subcortical Origin & Dual Tangential Streams:** Cortical GABAergic interneurons originate subcortically in the ventral telencephalon (medial and caudal ganglionic eminences, MGE/CGE) and migrate tangentially parallel to the ventricular surface along two prominent streams: a deep stream (near VZ/intermediate zone) and a superficial stream (through the marginal zone / Layer I).
   > - **Phenotypic Differentiation En Route:** Tangentially migrating interneurons mature while migrating—synthesizing GABA, extending leading processes, and displaying premature excitability.
   > - **Switch from Tangential Wandering to Radial Insertion:** Interneurons exit tangential streams by switching trajectories perpendicularly to invade the cortical plate:
   >   - *Physical substrates:* Turning and radial ascent frequently utilize descending *corticofugal axon fibers* as physical guidance tracks, or form *homotypic cellular chains* (in the SVZ).
   >   - *Paracrine GABA as a brake:* Migrating interneurons release non-vesicular GABA en route, activating $\text{GABA}_A$ receptors, which depolarizes immature neurons (high intracellular chloride), triggering voltage-gated $\text{Ca}^{2+}$ influx that acts as an autocrine/paracrine brake to slow and terminate migration into the cortical plate.
   > - **Open Questions / Incomplete Mechanisms in Biology:** The precise molecular receptor-ligand pairs and the temporal match-maker linking an interneuron's birthdate to the specific laminar depth of co-temporal pyramidal neurons remain active areas of ongoing research.

6. **Homotypic Repulsion & Retinal/Cortical Mosaics**
   - How do specific neuronal subtypes (e.g., retinal ganglion cells, horizontal cells, or cortical interneurons) establish regular spatial mosaics with non-random spacing?
   - Is homotypic spacing driven by contact-mediated mechanical repulsion, short-range diffusible repulsants, or selective apoptosis of overcrowded neighbours?

   > **Literature Summary / Findings:**
   > - **Exclusion Zones & Statistical Regularity:** Autocorrelation of retinal mosaics (e.g., cholinergic amacrine and alpha/beta ganglion cells) fits a random spatial distribution constrained by a minimal spacing or **exclusion zone** between homotypic somata. Drawing exclusion distances from a Gaussian distribution closely reproduces biological spatial packing.
   > - **Dendritic Tiling & Territoriality:** Forward-pathway sensory neurons (cone bipolars, alpha/beta/midget/parasol RGCs) display contact-mediated self-avoidance and mutual territoriality: advancing dendrites retract upon contact with homotypic dendrites, creating non-overlapping tiles. In contrast, lateral interneurons (horizontal cells) exhibit substantial overlap (3–8× coverage) and gap junction coupling; starburst amacrine cells overlap in territory but fasciculate into regular bundles.
   > - **Multi-Mechanism Origin of Full Mosaic Precision:**
   >   - *Contact Lateral Inhibition (Notch–Delta):* Juxtacrine signaling among Voronoi neighbours forces adjacent progenitors into distinct fates, creating an initial salt-and-pepper pattern. However, computational models (Eglen & Willshaw, 2002) demonstrate that lateral inhibition alone is mathematically insufficient to reach biological mosaic regularity.
   >   - *Selective Apoptosis of Crowded Neighbours:* Apoptosis acts downstream of lateral inhibition, pruning excess or improperly spaced cells in dense clusters (triggered by local density sensors like extracellular purines/ATP), raising mosaic regularity from ~2–3 to biological levels (>4–6 regularity index).
   >   - *Mechanical Somal & Arbor Repulsion:* Physical repulsion and differential adhesion push neighboring homotypic cell bodies apart after migration.
   > - **Scope & Limits:** Detailed experimental mosaic models are established in the retina; equivalent quantitative mosaic rules for neocortical interneurons remain less characterized in classical textbooks.

---

### 3. Gene Regulatory Dynamics & Cell Fate Differentiation

7. **Contact-Dependent Lateral Inhibition (Notch–Delta)**
   - What are the minimal dynamical rules and kinetic timescales governing Notch–Delta lateral inhibition in breaking spatial symmetry among adjacent progenitors?
   - How does contact-mediated juxtacrine signalling interact with long-range morphogen gradients (e.g., Wnt, BMP, Sonic Hedgehog) during pattern formation?

   > **Literature Summary / Findings:**
   > - **Minimal Collier–Eglen–Willshaw Dynamical System:**
   >   - For cells $i$ on a lattice with contact Voronoi neighbours $K_i$, state variables are membrane **Delta** ligand $D_i(t)$ and activated **Notch** receptor $N_i(t)$:
   >     $$\frac{dD_i}{dt} = -D_i + g(\bar{N}_i), \quad \frac{dN_i}{dt} = -N_i + f(\bar{D}_i)$$
   >   - Average neighbor Delta: $\bar{D}_i = \frac{1}{|K_i|} \sum_{j \in K_i} D_j$.
   >   - Transactivation: $f(x) = \frac{x^k}{A + x^k}$ ($k \approx 2$, Hill function of neighbor Delta).
   >   - Repression: $g(x) = \frac{1}{1 + B x^k}$ (NotchICD/SU(H) drives HES/E(spl) bHLH repressors, shutting down proneural bHLH genes and Delta transcription).
   > - **Bistability & Symmetry Breaking:**
   >   - Progenitors start in a metastable state of mutual inhibition.
   >   - Stochastic fluctuations trigger positive feedback: small increases in $D_i$ elevate $\bar{N}_j$ in adjacent cells, repressing their $D_j \to 0$.
   >   - Cell $i$ escapes incoming Notch inhibition ($D_i \to 1$), adopting the primary fate (proneural surge, cell-cycle exit, neurogenesis), while neighbors adopt secondary fates (progenitor renewal / radial glia).
   > - **Multi-Scale Morphogen–Notch Hierarchy:**
   >   - *Long-range morphogens (SHH, BMPs, Wnts):* Form macroscopic continuous gradients over dozens of cell diameters, inducing concentration-dependent homeodomain transcription factors (e.g. *Pax6*, *Nkx6.1*, *Dbx1/2*) that gate *proneural gene competence* in regional clusters (*Ngn1/2*, *Ascl1*, *Atoh1*).
   >   - *Juxtacrine Notch–Delta:* Operates within these morphogen-competence fields at the single-cell / sub-cellular scale. It prevents catastrophic simultaneous differentiation, maintaining a steady, self-renewing radial glial progenitor pool across iterative developmental cycles.

8. **Co-Transmission & Dale's Law in Developing Circuits**
   - Does neurotransmitter identity (excitatory vs. inhibitory) remain strictly invariant across a neuron's lifetime, or can immature neurons co-release multiple transmitters (e.g., GABA and glycine, or glutamate and dopamine) or flip functional polarity during development?
   - Under what developmental conditions do neurotransmitter receptors produce inverse electrical effects (e.g., depolarizing vs. hyperpolarizing GABA due to high intracellular chloride driven by NKCC1 vs. KCC2 expression)?

   > **Literature Summary / Findings:**
   > - **Violations of Dale's Principle & Co-Release:**
   >   - Neurons routinely co-release classical fast neurotransmitters alongside neuropeptides (substance P, somatostatin) or ATP.
   >   - Specific developing/mature circuits co-release multiple classical small-molecule transmitters: *Glutamate + Acetylcholine* in spinal descending interneurons (dINs), *GABA + Glycine* in ascending interneurons (aINs), and *Dopamine + Norepinephrine* in shared adrenergic terminals.
   >   - Immature projection systems can transiently accumulate non-synthesized transmitters (e.g., thalamocortical axons transiently expressing 5-HTT and VMAT to take up serotonin during whisker barrel formation).
   > - **Postsynaptic Receptor-Driven Divergence:** A single presynaptic transmitter produces opposing signs depending on target receptor complement: photoreceptor glutamate depolarizes OFF-bipolars (ionotropic AMPA/kainate) while hyperpolarizing ON-bipolars (metabotropic mGluR6 closing cGMP channels).
   > - **Developmental Transporter Switch (NKCC1 $\to$ KCC2):**
   >   - Fast GABAergic/glycinergic polarity is set by the chloride driving force: $I_{\text{Cl}} = g_{\text{Cl}}(V_m - E_{\text{Cl}})$.
   >   - *Immature neurons (NKCC1 predominance):* High expression of $\text{Na}^+\text{-K}^+\text{-2Cl}^-$ cotransporter actively concentrates $[\text{Cl}^-]_i$, shifting $E_{\text{Cl}}$ positive relative to $V_{\text{rest}}$. Opening $\text{GABA}_A$ causes $\text{Cl}^-$ efflux, triggering **depolarization and $\text{Ca}^{2+}$ influx**, which suppresses progenitor proliferation and modulates cell motility.
   >   - *Mature neurons (KCC2 upregulation):* Upregulation of $\text{K}^+\text{-Cl}^-$ cotransporter extrudes $\text{Cl}^-$, depressing $[\text{Cl}^-]_i$ and driving $E_{\text{Cl}}$ to $\sim -75\text{ mV}$, below $V_{\text{rest}}$, transforming GABA into a classical hyperpolarizing, inhibitory transmitter.
   >   - *Specialized adaptations:* Mature avian auditory coincidence detectors (nucleus laminaris) and retinal ON-bipolars retain elevated $[\text{Cl}^-]_i$ via persistent NKCC, while neuropathic injury can cause KCC2 downregulation and pathological depolarizing GABA.

---

### 4. Axon Guidance & Synaptogenesis

9. **Target Finding: Gradients vs. Intermediate Guideposts**
   - Do growing axons reach distant targets predominantly through continuous chemotropic morphogen gradients (e.g., Ephrins, Netrins, Semaphorins, Slits), or through discrete, multi-step navigation between intermediate guidepost cells?
   - How do axon growth cones reset their sensitivity after reaching an intermediate landmark (such as the midline or optic chiasm)?

   > **Literature Summary / Findings:**
   > - **Modular Multi-Step Trajectories vs. Long-Distance Gradients:**
   >   - Continuous diffusible gradients (Netrins, Slits) operate effectively only across short distances ($\le 1\text{–}2\text{ mm}$); long-distance axonal trajectories are therefore partitioned into sequential, shorter segments connecting intermediate guidepost cells or choice points.
   >   - Pioneer axons blaze intermediate pathways (e.g., grasshopper Ti1 pioneers making 90° turns at guideposts), while follower axons navigate via contact-mediated *fasciculation* (homophilic CAMs like Fasciclin I/II), with local anti-adhesive signals (e.g., BEATs) triggering defasciculation at targets.
   > - **Dynamic Sensitivity Reset at Choice Points:**
   >   - *The Ventral Midline Switch:* Approaching growth cones express DCC (attracted by floor plate Netrin-1) while Robo (Slit receptor) is intracellularly trapped/degraded (by Comm in flies, Rig-1 in mice). Upon crossing the midline, Comm/Rig-1 downregulates, Robo rapidly mobilizes to the growth cone surface, DCC is silenced and internalized, and high Slit chemorepulsion expels the growth cone forward, preventing recrossing. Post-crossing axons then switch to an orthogonal, ascending longitudinal gradient (e.g., Wnt4/Frizzled-3).
   >   - *Differential Receptor Gating (Optic Chiasm):* Ventrotemporal RGCs express ZIC2 $\to$ high EphB1, mediating contact repulsion against midline ephrinB to divert into the ipsilateral tract; contralateral RGCs express Islet2, repressing ZIC2/EphB1 to cross unhindered.
   > - **Local Cytoskeletal & Translational Autonomy:** Growth cones surgically severed from cell bodies continue navigating and turning accurately; sensitivity resets are mediated locally through *local cytoplasmic protein translation* and calcium transients via TRP channels governing rapid actin/microtubule reorganization.

10. **Topographic Mapping & Chemoaffinity**
    - What mathematical models (e.g., Sperry's chemoaffinity hypothesis, Preston-Willshaw, or competitive marker induction) best predict topographic projection fidelity under varying target-to-source tissue size ratios?
    - How do molecular gradients interact with activity-dependent mechanisms to establish precise retinotopic or tonotopic maps?

    > **Literature Summary / Findings:**
    > - **Systems-Matching & Failure of Rigid Chemoaffinity:**
    >   - Surgical mismatch experiments (half-retina expanding over a full tectum, intact retina compressing into a half-tectum) prove that target matching is dynamically regulated rather than fixed.
    >   - *Sperry Type I (rigid lock-and-key):* Fails systems-matching because fixed address tags leave unoccupied target territory permanently empty.
    >   - *Prestige–Willshaw Type II (graded affinities + finite contact limits):* Affinities represent contact lifetimes across continuous gradients; dynamic competition for limited synaptic slots produces expansion/compression without changing molecular markers.
    >   - *Gierer Countergradient Energy Model:* Combines guidance force $-\nabla p(x)$ with an accumulated terminal density term $r(x, t)$ ($dr/dt = \varepsilon \rho(x)$). Crowding builds an repulsive pressure that pushes terminals into unoccupied target territory until density equilibrates.
    >   - *Marker Induction (von der Malsburg & Willshaw):* Axons dynamically inject markers into target cells with lateral target diffusion ($\beta \nabla^2 T$) and synaptic normalization ($\Delta S_{ij} \propto \Phi_{ij} / \sum \Phi$), allowing the target to continuously rescale its coordinates to match incoming axon pools.
    > - **Two-Stage Hierarchical Topographic Assembly:**
    >   - *Stage 1: Molecular Guidance (Activity-Independent):* Orthogonal countergradients (e.g. retinal EphA vs. tectal ephrin-A nasotemporal; EphB vs. ephrin-B mediolateral) establish coarse topographic polarity and approximate target regions, but leave terminal arbors broad, exuberant, and overlapping.
    >   - *Stage 2: Activity-Dependent Refinement (Correlated Spontaneous Bursts):* Pre-sensory spontaneous bursts (cholinergic/glutamatergic retinal waves, spontaneous cochlear bursts) convert spatial proximity into temporal synchrony. Neighboring afferents firing together stabilize and strengthen synapses through NMDA-dependent Hebbian LTP / tLTP (STDP). Uncorrelated/asynchronous inputs undergo tLTD and competitive elimination via Mexican-hat lateral inhibition / sum-rule constraints, pruning exuberant branches into sharp, single-cell receptive fields (and eye-specific stripes in three-eyed frog assays).

11. **Multi-Synapse Connectivity & Bouton Distribution**
    - When an axon forms functional connections with a dendritic arbor, what determines whether it establishes a single synaptic contact versus multiple distributed synaptic boutons?
    - How does physical proximity constrain the probability of functional synaptogenesis (Peters' rule vs. cell-type-specific partner selection)?

    > **Literature Summary / Findings:**
    > - **Bouton Distribution is Cell-Type & Computationally Programmed:**
    >   - *Excitatory-to-Excitatory (Sparse, 1–few contacts):* Pyramidal–pyramidal and parallel fiber–Purkinje projections form only 1–2 boutons per target cell, distributing sparse contacts across thousands of distinct afferents to maximize combinatorial associative capacity and prevent input monopolization.
    >   - *Inhibitory Interneurons (Dense, multi-bouton clusters):* Basket cells systematically place 10–30 boutons across the soma and proximal dendrites; chandelier cells form vertical "cartridges" of up to 40 symmetric boutons along the axon initial segment (AIS).
    >   - *Specialized High-Gain Afferents:* Single hippocampal mossy fibers make up to 37 active zones on a single CA3 thorny excrescence; Group Ia afferents place ~10–35 boutons on motoneuron arbors; endbulbs of Held deploy >100 release sites to raise transmission safety factors given low quantal release probability ($P_r \approx 0.1\text{–}0.2$).
    >   - *Electrotonic Dispersion:* Spreading multiple boutons across distinct dendritic branches avoids local driving-force saturation that occurs when contacts cluster on the same electrotonic compartment.
    > - **Reconciling Peters' Rule with Molecular Specificity (Two-Stage Model):**
    >   - *Peters' Rule (Proximity as Necessary Geometric Substrate):* Spatial overlap of axonal and dendritic arbors defines potential connectivity and distance-dependent connection decay ($P_{\text{conn}} \to 0$ over hundreds of $\mu\text{m}$).
    >   - *Breakdown of Pure Proximity:* Intermingled neurons in identical volumes bypass thousands of overlapping dendrites (extreme sparsity in striatum; highly coupled subnetwork clustering in cortex; subcellular targeting to AIS vs. spines).
    >   - *Two-Stage Assembly:*
    >     1. *Geometric Exploration:* Random filopodial outgrowth samples overlapping territory.
    >     2. *Molecular & Activity Validation:* Transmembrane recognition molecules (Neurexin–Neuroligin-1 on excitatory spines vs. Neuroligin-2 on inhibitory shafts/somata; Cadherins) trigger local $\text{Ca}^{2+}$ transients to induce postsynaptic density assembly. Inappropriate contacts fail to evoke $\text{Ca}^{2+}$ transients and retract within minutes, followed by activity-dependent STDP consolidation or pruning.

---

### 5. Spontaneous Activity, Refinement & Homeostasis

12. **Spontaneous Developmental Waves & Bursts**
    - Prior to sensory experience (e.g., in embryonic or early postnatal retina, cochlea, and spinal cord), what generates large-scale propagating waves of spontaneous activity?
    - What cellular pacemakers, network architectures, and refractory recovery mechanisms dictate the velocity, initiation frequency, and spatial boundary of these waves?

    > **Literature Summary / Findings:**
    > - **Pacemaking & Network Drivers Across Sensory & Motor Systems:**
    >   - *Early Retina:* Initiated by lateral cholinergic starburst amacrine pacemakers, propagating via acetylcholine (nicotinic receptors) and early depolarizing GABA/glycine (NKCC1 chloride phenotype), electrical gap junctions, and extracellular potassium shifts.
    >   - *Developing Spinal Cord:* Rhythmic locomotor pacemaking driven by an excitatory interneuron core using persistent sodium ($I_{\text{Na,p}}$), low-threshold T-type calcium ($I_T$), and hyperpolarization-activated cation ($I_h$) conductances coupled via AMPA, NMDA, and gap junctions.
    >   - *Auditory Pathway & Cochlea:* Endogenous bursts originating in the immature sensory epithelium drive patterned bursts through spiral ganglion neurons, calibrating tonotopic maps and binaural coincidence circuits before hearing onset.
    > - **Wave Kinetics, Propagation Velocity & Refractory Mechanics:**
    >   - *Initiation & Limit Cycles:* Driven by slow inward currents ($I_h$, $I_{\text{Na,p}}$, $I_T$) triggering periodic transitions into bursting.
    >   - *Burst Termination & Inter-Wave Interval:* Intracellular $\text{Ca}^{2+}$ and $\text{Na}^+$ accumulation activates calcium- and sodium-activated potassium currents ($\text{sK}_{\text{Ca}}$, $\text{fK}_{\text{Ca}}$, $\text{K}_{\text{Na}}$), generating prolonged afterhyperpolarizations (AHPs) lasting seconds. Wave re-initiation is gated by the time required for $\text{Ca}^{2+}$ clearance, $\text{K}^+$ channel deactivation, and recovery of voltage-gated $\text{Na}^+$ channels from inactivation.
    >   - *Velocity & Spatial Footprint:* Wavefront velocity $v$ is governed by the spatial coupling kernel $w(|\mathbf{r} - \mathbf{r}'|)$ (recurrent collaterals, starburst arbors, gap junctions), membrane time constant $\tau_m$, and firing threshold $\vartheta$ ($u(vt, y, t) = \vartheta$).
    >   - *Boundaries & Mutual Annihilation:* Propagating wavefronts do not reflect; they terminate where recurrent coupling attenuates or upon colliding with refractory tissue left in the wake of another wave (mutual annihilation).

13. **Activity-Dependent Pruning vs. Neurotrophic Apoptosis**
    - What distinguishes the mechanisms of early neuronal apoptosis (regulated by target-derived neurotrophins such as NGF and BDNF) from subsequent synapse-level pruning (regulated by classic STDP, microglia/complement cascade, or heterosynaptic depression)?
    - Does synaptic elimination require direct competitive punishment of silent synapses, or simple withdrawal of positive trophic stabilization?

    > **Literature Summary / Findings:**
    > - **Different Spatial Scales and Execution Targets:**
    >   - *Early Apoptosis (Whole-cell death):* Adjusts global population size to match target tissue volume (15–50% of neurons die shortly after migration). Target tissues secrete limited neurotrophins (NGF, BDNF) binding Trk receptors, retrogradely transported along microtubules to somatic nuclei to repress cell-death genes. Deprivation activates caspase cascades (caspase-9), executing programmed cell death.
    >   - *Synapse Pruning (Subcellular remodeling):* Refines exuberant connectivity (~50% more synapses in infant than adult cortex) without killing the parent neuron. Presynaptic/postsynaptic decorrelation drives AMPA receptor endocytosis, PSD-95 degradation, and terminal retraction, with debris cleared by microglial phagocytosis.
    > - **Elimination Requires Active Competitive Punishment:**
    >   - *Biological Proof:* Passive disuse alone does not eliminate synapses. Globally silencing all receptors (e.g. blocking all NMDA/AChRs) arrests pruning and preserves multi-innervation. Pruning occurs only when active competitors depolarize the postsynaptic target (e.g. focal $\alpha$-bungarotoxin blockade at the NMJ or monocular deprivation in striate cortex, where active open-eye inputs actively displace silent closed-eye inputs).
    >   - *Computational & Mathematical Proof:* Passive competition models fail to eliminate isolated inactive terminals once sole occupancy is reached. Robust single-innervation models require active punishment: acausal spike arrivals drive timing-dependent depression (tLTD) via subtractive constraints, destabilizing silent or uncorrelated synapses below elimination thresholds.

14. **Homeostatic Synaptic Plasticity & Firing Rate Setpoints**
    - How do developing neurons distinguish between transient physiological burst patterns and chronic hypo-/hyper-activity to adjust their overall synaptic strengths (synaptic scaling) or intrinsic membrane excitability?
    - Are homeostatic weight adjustments multiplicative across all synapses, or are they locally compartmentalized on dendritic branches?

    > **Literature Summary / Findings:**
    > - **Temporal & Spatial Calcium Filtering (Bursts vs. Chronic Deviations):**
    >   - *Multi-Timescale Sensor Integration (Liu–Abbott formalism):* Neurons track activity via distinct temporal sensors (fast ms, intermediate tens of ms, slow minutes/hours). High-frequency burst patterns simply oscillate around target setpoints in fast sensors; only chronic deviations produce persistent net error $(\bar{S}_{\text{slow}} - S_{\text{slow}} \neq 0)$, driving homeostatic compensation without reacting to normal bursts.
    >   - *Microdomains vs. Bulk/Nuclear Calcium:* Physiological bursts produce high, transient $\text{Ca}^{2+}$ spikes ($10\text{–}100\ \mu\text{M}$) confined to spine heads and active zone microdomains (triggering CaMKII/Hebbian plasticity). Chronic hyperactivity saturates cytosolic buffers (calbindin, parvalbumin), spilling into the bulk cytoplasm and nucleus to recruit calcineurin–NFAT and CaMKIV–CREB cascades, altering channel and AMPA gene transcription over hours/days.
    >   - *BDNF/TrkB Setpoint Tuning:* Elevated chronic activity prompts BDNF release, triggering homeostatic depression of recurrent excitatory weights while scaling up somatic inhibition.
    > - **Global Multiplicative Scaling vs. Local Dendritic Normalization:**
    >   - *Multiplicative Scaling (Turrigiano et al.):* Cell-wide scaling multiplies all excitatory inputs by a scalar $\gamma$ ($w_i \to \gamma w_i$). This stabilizes global activity while preserving relative synaptic weight ratios ($w_i / w_j$) established by prior Hebbian learning, protecting stored information.
    >   - *Local Dendritic Compartmentalization:* Individual dendritic branches operate as semi-independent processing subunits with local spine-head isolation and distance-dependent synaptic scaling (distal synapses express larger AMPA densities to counterbalance passive dendritic cable attenuation and equalize somatic impact).

---

### 6. Myelination & Conduction Velocity Tuning

15. **Myelination Onset and Activity Dependence**
    - What cues trigger oligodendrocytes or Schwann cells to initiate myelin sheath deposition along specific axon segments?
    - To what extent is myelination regulated by spike activity versus physical axon diameter and length?
    - How does selective myelination synchronize conduction delays across pathways of unequal physical distance (e.g., in auditory coincidence detection or transcallosal projections)?

    > **Literature Summary / Findings:**
    > - **Glial-Axon Interactions & Initiation Cues:**
    >   - Oligodendrocytes (CNS, single cell wrapping multiple axons) and Schwann cells (PNS, 1:1 segment wrapping) differentiate and compact membranes via myelin basic protein (MBP) and proteolipid protein (PLP).
    >   - The exact primary molecular trigger activating the "myelination program" remains incompletely identified; in adult CNS, myelin also presents inhibitory molecules (Nogo) that block regeneration.
    > - **Caliber Thresholds, Conduction Direction & Systemic Timetables:**
    >   - *Hursh Factor & Caliber Thresholds:* Unmyelinated conduction velocity scales as $\sqrt{d}$; myelinated velocity scales linearly: $\text{Velocity (m/s)} \approx 6 \times \text{Axon Diameter (}\mu\text{m)}$.
    >   - *The $1\ \mu\text{m}$ Efficiency Limit:* For axons smaller than $\sim 1\ \mu\text{m}$ (e.g. C fibers, $0.2\text{–}1.5\ \mu\text{m}$ conducting at $0.5\text{–}2\text{ m/s}$), myelination is geometrically and metabolically inefficient; thin fibers remain unmyelinated.
    >   - *Myelogenetic Sequence:* Myelination follows the direction of impulse conduction (proximal tracts before distal), with hierarchical progression: primary sensory before motor in CNS, primary projection before intracortical association, and occipital before frontotemporal.
    > - **Delay Synchronization in Asymmetric Networks:**
    >   - *Binaural Coincidence Detection (Jeffress Delay Lines):* In auditory interaural time difference (ITD) circuits (medial superior olive / nucleus laminaris), contralateral and ipsilateral axons vary in physical length. Axons act as anatomical delay lines, while activity-dependent STDP selects and stabilizes delay lines that coincide with postsynaptic firing to achieve sub-millisecond arrival synchrony.
    >   - *Interhemispheric / Callosal Synchronization:* Long-range commissural fibers (corpus callosum body/splenium before genu/rostrum) use accelerated myelination and axon diameter variation to compensate for physical distance disparities and synchronize bilateral cortical oscillations.

---

REFS:

1.

Calegari, F., & Huttner, W. B. (2003). An inhibition of cyclin-dependent kinases that lengthens, but does not arrest, neuroepithelial cell cycle induces premature neurogenesis. *Journal of Cell Science*, 116(24), 4947–4955.
Caviness, V. S., Jr., Takahashi, T., & Nowakowski, R. S. (1995). Numbers, time and neocortical neuronogenesis: A general model. *Trends in Neurosciences*, 18(9), 379–383.
Chenn, A., & Walsh, C. A. (2002). Regulation of cerebral cortical size by control of cell cycle exit in neural precursors. *Science*, 297(5580), 365–369.
Götz, M., & Huttner, W. B. (2005). The cell biology of neurogenesis. *Nature Reviews Molecular Cell Biology*, 6(10), 777–788.
Zhong, W., Feder, J. N., Jiang, M. M., Jan, L. Y., & Jan, Y. N. (1996). Asymmetric localization of a mammalian numb homolog during neurogenesis. *Neuron*, 17(1), 43–53.

2.

Isshiki, T., Pearson, B., Holbrook, S., & Doe, C. Q. (2001). Drosophila neuroblasts sequentially express transcription factors which specify the temporal identity of their neuronal progeny. *Cell*, 106(4), 511–521.
McConnell, S. K. (1995). Constructing the cerebral cortex: Neurogenesis and fate determination. *Neuron*, 15(4), 761–768.
McConnell, S. K., & Kaznowski, C. E. (1991). Cell cycle dependence of laminar determination in cultured neocortical precursors. *Science*, 254(5029), 282–285.
Shen, Q., Wang, Y., Dimos, J. T., Fasano, C. A., Phoenix, T. N., Lemischka, I. R., Ivanova, N. B., Stifani, S., Morrisey, E. E., & Temple, S. (2006). The timing of cortical neurogenesis is encoded within lineages of individual progenitor cells. *Nature Neuroscience*, 9(6), 743–751.

3.

Haydar, T. F., Ang, E., Jr., & Rakic, P. (2003). Mitotic spindle rotation and mode of cell division in the developing telencephalon. *Proceedings of the National Academy of Sciences*, 100(5), 2890–2895.
Miyata, T., Kawaguchi, A., Okano, H., & Matsuzaki, F. (2001). Asymmetric inheritance of radial glial fibers by cortical neurons. *Neuron*, 31(5), 727–741.
Sanada, K., & Tsai, L. H. (2005). G protein βγ subunits and AGS3 control spindle orientation and asymmetric cell fate of cerebral cortical progenitors. *Cell*, 122(1), 119–131.
Sauer, F. C. (1935). Mitosis in the neural tube. *Journal of Comparative Neurology*, 62(2), 377–405.

4.

Caviness, V. S., Jr., & Sidman, R. L. (1973). Time of origin of corresponding cell classes in the cerebral cortex of normal and reeler mutant mice: An autoradiographic analysis. *Journal of Comparative Neurology*, 148(2), 141–151.
D'Arcangelo, G., Miao, G. G., Chen, S. C., Soares, H. D., Morgan, J. I., & Curran, T. (1995). A protein related to extracellular matrix proteins deleted in the mouse mutant reeler. *Nature*, 374(6524), 719–723.
Rakic, P. (1972). Mode of cell migration to the superficial layers of fetal monkey neocortex. *Journal of Comparative Neurology*, 145(1), 61–83.
Zheng, C., Heintz, N., & Hatten, M. E. (1996). CNS gene encoding astrotactin, which supports neuronal migration along radial glia. *Science*, 272(5260), 417–419.

5.

Anderson, S. A., Eisenstat, D. D., Shi, L., & Rubenstein, J. L. (1997). Interneuron migration from basal forebrain to neocortex: Dependence on Dlx genes. *Science*, 278(5337), 474–476.
Behar, T. N., Schaffner, A. E., Scott, C. A., O'Connell, C., & Barker, J. L. (1998). Differential response of cortical plate and ventricular zone cells to GABA as a migration stimulus. *Journal of Neuroscience*, 18(16), 6378–6387.
Bortone, D., & Polleux, F. (2009). KCC2 expression promotes the termination of cortical interneuron migration in a voltage-sensitive calcium-dependent manner. *Neuron*, 62(1), 53–71.
Marín, O., & Rubenstein, J. L. (2001). A long, remarkable journey: Tangential migration in the telencephalon. *Nature Reviews Neuroscience*, 2(11), 780–790.

6.

Eglen, S. J., & Willshaw, D. J. (2002). Influence of cell fate mechanisms upon retinal mosaic formation: a modelling study. *Development*, 129(23), 5399–5408.
Fuerst, P. G., Koizumi, A., Masland, R. H., & Burgess, R. W. (2008). Neurite branching and mosaic mosaicism require DSCAM. *Nature*, 451(7177), 470–474.
Galli-Resta, L., Resta, G., Tan, S. S., & Reese, B. E. (1997). Mosaics of Islet-1-expressing amacrine cells assembled by short-range cellular interactions. *Journal of Neuroscience*, 17(20), 7831–7838.
Wässle, H., & Riemann, H. J. (1978). The mosaic of nerve cells in the mammalian retina. *Proceedings of the Royal Society of London. Series B. Biological Sciences*, 200(1141), 441–461.

7.

Artavanis-Tsakonas, S., Rand, M. D., & Lake, R. J. (1999). Notch signaling: Cell fate control and signal integration in development. *Science*, 284(5415), 770–776.
Collier, J. R., Monk, N. A., Maini, P. K., & Lewis, J. (1996). Pattern formation by lateral inhibition with feedback: A mathematical model of Delta–Notch signalling. *Journal of Theoretical Biology*, 183(4), 429–446.
Kageyama, R., Ohtsuka, T., Shimojo, H., & Imayoshi, I. (2008). Dynamic Notch signaling in neural progenitor cells and a modern view of hairy, Hes and basic helix-loop-helix transcription factors. *Development, Growth & Differentiation*, 50(Suppl. 1), S87–S102.

8.

Ben-Ari, Y. (2002). Excitatory actions of GABA during development: The nature of the nurture. *Nature Reviews Neuroscience*, 3(9), 728–739.
Jonas, P., Bischofberger, J., & Sandkühler, J. (1998). Corelease of two fast neurotransmitters at a central synapse. *Science*, 281(5375), 419–424.
Rivera, C., Voipio, J., Payne, J. A., Ruusuvuori, E., Lahtinen, H., Lamsa, K., Pirvola, U., Saarma, M., & Kaila, K. (1999). The K+/Cl- co-transporter KCC2 renders GABA hyperpolarizing during neuronal maturation. *Nature*, 397(6716), 251–255.

9.

Bentley, D., & Caudy, M. (1983). Pioneer axons lose directed growth in the absence of guidepost cells. *Nature*, 304(5921), 62–65.
Dickson, B. J. (2002). Molecular mechanisms of axon guidance. *Science*, 298(5600), 1959–1964.
Herrera, E., Brown, L., Aruga, J., Rachel, R. A., Dolen, G., Mikoshiba, K., Brown, S., & Mason, C. A. (2003). Zic2 patterns binocular vision by specifying the uncrossed retinal projection. *Cell*, 114(5), 545–557.
Kennedy, T. E., Serafini, T., de la Torre, J. R., & Tessier-Lavigne, M. (1994). Netrins are diffusible chemotropic factors for commissural axons in the embryonic spinal cord. *Cell*, 78(3), 425–435.
Long, H., Sabatier, C., Ma, L., Plump, A., Yuan, W., Ornitz, D. M., Tamada, A., Murakami, F., Rao, Y., Lindberg, F. P., & Tessier-Lavigne, M. (2004). Conserved roles for Slit and Robo proteins in midline commissural axon guidance. *Neuron*, 42(2), 213–223.

10.

Gierer, A. (1983). Model for the retino-tectal projection. *Proceedings of the Royal Society of London. Series B. Biological Sciences*, 218(1210), 77–93.
McLaughlin, T., & O’Leary, D. D. (2005). Molecular gradients and development of retinotopic maps. *Annual Review of Neuroscience*, 28, 327–355.
Prestige, M. C., & Willshaw, D. J. (1975). On a role for competition in the formation of patterned neural connexions. *Proceedings of the Royal Society of London. Series B. Biological Sciences*, 190(1098), 77–98.
Sperry, R. W. (1963). Chemoaffinity in the orderly growth of nerve fiber patterns and connections. *Proceedings of the National Academy of Sciences*, 50(4), 703–710.
von der Malsburg, C., & Willshaw, D. J. (1977). How to label nerve cells so that they can interconnect in an ordered fashion. *Proceedings of the National Academy of Sciences*, 74(11), 5176–5178.

11.

Braitenberg, V., & Schüz, A. (1998). *Cortex: Statistics and geometry of neuronal connectivity* (2nd ed.). Springer-Verlag.
Somogyi, P., Tamás, G., Lujan, R., & Buhl, E. H. (1998). Salient features of synaptic organisation in the cerebral cortex. *Brain Research Reviews*, 26(2–3), 113–135.
Südhof, T. C. (2008). Neuroligins and neurexins link synaptic function to cognitive disease. *Nature*, 455(7215), 903–911.
Waites, C. L., Craig, A. M., & Garner, C. C. (2005). Mechanisms of vertebrate synaptogenesis. *Annual Review of Neuroscience*, 28, 251–274.

12.

Feller, M. B., Wellis, D. P., Stellwagen, D., Werblin, F. S., & Shatz, C. J. (1996). Requirement for cholinergic synaptic transmission in the generation of spontaneous retinal waves. *Science*, 272(5265), 1182–1187.
Meister, M., Wong, R. O., Baylor, D. A., & Shatz, C. J. (1991). Synchronous bursts of action potentials in ganglion cells of the developing mammalian retina. *Science*, 252(5008), 939–943.
O’Donovan, M. J., Chub, N., & Wenner, P. (1998). Mechanisms of spontaneous activity in developing spinal networks. *Journal of Neurobiology*, 37(1), 131–145.
Tritsch, N. X., Yi, E., Gale, J. E., Glowatzki, E., & Bergles, D. E. (2007). The origin of spontaneous activity in the developing auditory system. *Nature*, 450(7166), 50–55.

13.

Balice-Gordon, R. J., & Lichtman, J. W. (1994). Long-term synapse loss induced by focal blockade of postsynaptic receptors. *Nature*, 372(6506), 519–524.
Oppenheim, R. W. (1991). Cell death during development of the nervous system. *Annual Review of Neuroscience*, 14(1), 453–501.
Purves, D., & Lichtman, J. W. (1980). Elimination of synapses in the developing nervous system. *Science*, 210(4466), 153–157.
Stevens, B., Allen, N. J., Vazquez, L. E., Howell, G. R., Christopherson, K. S., Nouri, N., Kerr, D., Adaller, A., Welzel, P. E., & Barres, B. A. (2007). The classical complement cascade mediates CNS synapse elimination. *Cell*, 131(6), 1164–1178.

14.

Leslie, K. R., Nelson, S. B., & Turrigiano, G. G. (2001). Postsynaptic depolarization scales quantal amplitude in cortical pyramidal neurons. *Journal of Neuroscience*, 21(20), RC170.
Liu, Z., Golowasch, J., Marder, E., & Abbott, L. F. (1998). A model neuron with activity-dependent conductances regulated by multiple calcium sensors. *Journal of Neuroscience*, 18(7), 2309–2320.
Turrigiano, G. G., Leslie, K. R., Desai, N. S., Rutherford, L. C., & Nelson, S. B. (1998). Activity-dependent scaling of quantal amplitude in neocortical neurons. *Nature*, 391(6670), 892–896.
Turrigiano, G. G., & Nelson, S. B. (2004). Homeostatic plasticity in the developing nervous system. *Nature Reviews Neuroscience*, 5(2), 97–107.

15.

Barres, B. A., & Raff, M. C. (1999). Axonal control of oligodendrocyte development. *Journal of Cell Biology*, 147(6), 1123–1128.
Flechsig, P. (1920). *Anatomie des menschlichen Gehirns und Rückenmarks auf myelogenetischer Grundlage*. Georg Thieme.
Seidl, A. H., Sanchez, J. T., Yamoah, E. N., & Rubel, E. W. (2010). Neural delay lines are adjusted for accurate binaural hearing. *Proceedings of the National Academy of Sciences*, 107(26), 11999–12004.
Waxman, S. G., & Bennett, M. V. (1972). Relative conduction velocities of small myelinated and non-myelinated fibres in the central nervous system. *Nature New Biology*, 238(85), 217–219.


# Page: development/roadmap.md

## neurogenesis-sim roadmap

This roadmap turns the current toolbox (body plan → one fixed growth procedure → spiking network) into a **versatile
developmental engine**. A user must be able to:
- **build everything by hand** (supervised: shapes, layers, cell positions, connections all specified);
- **let genes do everything** (unsupervised: a genome, development and evolution);
- **or anything in between** (semi-supervised): for example a hand-drawn cortex with its layers and cytochrome-oxidase
  blobs or orientation pinwheels, on which genes control proliferation, migration, differentiation and wiring.

Development is an **arbitrary, ordered list of stages** (for example: 1 proliferation, 2 migration, 3 differentiation,
4 proliferation of differentiated cells, 5 axon growth, 6 activity-dependent pruning ...). Each stage, and each
parameter of each stage, can be set by hand, controlled by genes, read from a hand-drawn field, or fitted to data.

Everything must stay **cheap**: development and simulation scale linearly with the number of cells and synapses, run on
a laptop CPU for small systems, and say what they will cost before running (neurogenesis.cost).

Tasks are written so that different agents can work on them in parallel. Read `AGENTS.md` first.

---

### 1. Target architecture

```
Project (JSON + .npz, versioned schema)
├── Geometry          shapes in 2D / 3D: primitives, meshes, voxel masks; coordinate frames (e.g. cortical depth)
├── Fields            hand-made or generated data on the geometry: scalar (layer boundaries, morphogen gradients),
│                     vector (orientation, migration directions), labels (areas, layers), point sets (pinwheel
│                     centres, blob centres) — sampled at any position
├── Genome            nested dict of genes (existing neurogenesis.genome)
├── Schedule          ordered list of Stage configs, possibly repeated or looped
│     Stage = operator + parameters; every parameter is a Param:
│             const(value) | gene(path, low, high) | field(name, transform) | fit(target, metric) | expr(formula)
├── Data Sources      (optional) public connectome / reconstruction datasets & filters (Phase I)
├── Pharmacology      (optional) profile reference & transmitter-receptor dynamics (Phase P)
├── Morphology        (optional) branching neurite & arbor configurations (Phase N)
└── Plan              (existing BodyPlan) still usable: a plan is compiled into a default schedule

Tissue (struct of arrays, the state that stages transform)
  position (N, 3), parent / lineage id, birth stage, cell type, region label, expression (N, K genes),
  sign (exc / inh), neuron parameters, alive flag,
  transmitter (N, int8 bitmask / list), receptor_expression (N, R, float32),
  morphology (optional ArborStore: nodes, compartments, branches)  +
  Connectivity (sparse: pre, post, weight, delay, receptor_class, optional pre_node, post_node, path_length, dendritic_distance)

Stages (operators: Tissue x Params x rng -> Tissue)
  seed_cells, proliferate, apoptosis, migrate, sort / layer, mosaic, express (gene regulatory step), differentiate,
  identity, grow_axons (guidance rules), synaptogenesis, prune, refine_by_activity (waves, twitches, STDP),
  myelinate, scale (homeostasis), place_by_hand, connect_by_hand, freeze

Compiler: Tissue -> SNNNetwork (existing spiking kernel and models) ; simulation and cost (existing)
Optimisers: evolution of genes (existing islands) ; fitting of `fit` params to targets ; mixed
GUI (viz/): geometry and field editor, schedule editor, step-by-step development preview, export / import Project
```

**The single most important rule: hand, gene, field and fit are interchangeable at the level of each parameter.**
- Supervised development means every `Param` is `const`, `field` or hand placement.
- Unsupervised development means `gene` params evolved.
- Semi-supervised development mixes them.
- `fit` params are optimised against data: cell densities, layer thickness, connection statistics, receptive fields.

### 2. Design principles (binding for every task)
1. **Reproducibility.** Same project, genome and development seed give the same tissue and network, bit for bit on a
   given platform.
   - Each stage draws from its own random stream, `np.random.default_rng([seed, stage_index, stage_salt])`. Adding,
     removing or reordering one stage must not change the random numbers of the others.
   - The legacy `grow()` keeps its single stream (backward compatibility, section 5).
2. **Backward compatibility.** Existing plans and genomes keep growing the same networks (fingerprint tests). New
   options default to "off".
3. **Generality.** No species, task or environment names in the core: no "retina", "foraging" or "cortex" in
   `neurogenesis/`. Domain content lives in examples, modules or user projects.
4. **Low compute.**
   - Every operator is O(N) or O(N log N) in cells and O(E) in synapses, using spatial hashing or cell lists for every
     local interaction.
   - **Never build a dense N × N matrix** except below a documented size threshold. The current `grow()` builds a dense
     Ns × Nd probability matrix per projection; task D2 removes this bottleneck.
   - Use float32, struct of arrays and vectorised NumPy or JAX; process large tissues in chunks.
5. **Cost first.** Each stage declares its cost (memory and time as functions of N and E). `neurogenesis.cost` sums
   them before a run, as it already does for simulations, and refuses what cannot fit.
6. **Data, not code, in projects.** A Project is a JSON (+ `.npz` for large arrays) that validates against a schema.
   Plugins (Python modules registering rules, stages or models) are listed by name and imported on load, as
   `BodyPlan.plugins` already is.
7. **Each part is testable alone.** Stages are pure functions of (tissue, params, rng); the GUI only edits and previews
   Project files; Python runs the full development.

### 3. Current state (2026-11-01)
| Area | Present | Missing |
|---|---|---|
| Spiking | integer LIF kernel, bit-identical in Python, JAX and the browser; `two_compartment` and `hh` models; batched runs; cost estimator with machine calibration; STDP, Hebbian and three-factor plasticity (E1) | per-region choice of model |
| Development | `BodyPlan` (regions, projections), one fixed growth order (placement → sign → differentiation → identity → axons), rule registry in 5 phases (placement, shift, target, probability, weight), knockouts, tonic currents; geometry, fields, `Tissue`, stage schedule (A1–A5, B1–B5, C1–C13, D2, D3; see `docs/STAGES.md`) | chunked growth (D4), and the parts of C8, D1 and D2 marked partial below |
| Genome / evolution | gene paths, [0, 1] coding, gene groups, JSON; island model, robust fitness, audits; `Param` sources (A3); fitting of `Fit` params (E2), inner-loop fitting and Pareto evolution (E3) | gradient-based fitting |
| Modules | save / load, composition, plugins, neuron model recorded; versions, local registry (G1); validation assays (E4) | a shared online catalogue |
| GUI | 3D viewer and per-neuron editing of a finished network | geometry and field editor, schedule editor, development preview, Project import / export |
| CI | GitHub Actions: tests on Python 3.10 and 3.12, browser-kernel equivalence, viewer build, cost command line, fixed CPU performance benchmarks (report only, regressions above 30% flagged) | coverage |

---

### 4. Tasks

Each task gives: **goal, deliverables, interfaces, acceptance tests, dependencies.**
- A task is done when its tests pass locally (and in CI when run) and its documentation is written. CI runs on every pull request (the repository is public).
- Interfaces marked **[contract]** are shared by several tasks: change them only through task A1, and announce it in the PR.

#### Phase A: foundations (start here; A1–A4 mostly sequential)

**A1. Project schema and I/O [contract].**
- **Deliverables:**
  - `neurogenesis/project/schema.py`: a JSON schema with a version field;
  - `Project.load` / `save` (JSON + `.npz` side file for large arrays);
  - migration functions between schema versions;
  - `docs/PROJECT_FORMAT.md`.
- **Contents:** geometry, fields, schedule, genome, optional plan, plugins, neuron model, metadata.
- **Tests:** round trip; schema rejects malformed files; version migration.
- **Dependencies:** none.

**A2. Tissue state [contract].**
- **Deliverables:** `neurogenesis/tissue.py`, a struct-of-arrays `Tissue`. Fields:
  - positions (N, 3) float32;
  - lineage (parent id), birth stage, cell type (int), region (int), expression (N, K) float32;
  - sign, neuron parameters (the `NeuronData` fields), alive mask;
  - optional sparse connectivity.
- **Operations:** add cells, kill (mask then compact), subset, concatenate, and `to_network()` → `SNNNetwork`.
- **Tests:** compaction keeps lineage consistent; `to_network` round trip; memory per cell documented.
- **Dependencies:** none.

**A3. Param sources [contract].**
- **Deliverables:** `neurogenesis/params.py`.
  - **Types:** `Param = Const | Gene(path, lo, hi, integer) | Field(name, transform) | Fit(target, init, lo, hi) | Expr(formula)`.
  - **Resolution:** `resolve(param, genome, fields, positions)` returns a scalar or a per-cell array.
  - **Evolution:** the genes of a schedule are collected automatically into the gene list used by evolution, so no
    hand-written gene lists are needed.
- **Tests:** each source; per-cell resolution through fields; gene collection equals the union of Gene params.
- **Dependencies:** A1.

**A4. Stage interface and scheduler [contract].**
- **Deliverables:** `neurogenesis/stages/base.py`, defining a stage as:
  - `Stage(name, operator, params: dict[str, Param], when=..., repeat=k)`;
  - an `@register_stage` registry;
  - `run_schedule(project, genome, seed) -> Tissue`, with one random stream per stage.
  - **Cost declaration per stage:** `cost(N, E, params) -> (bytes, ops)`, summed by `neurogenesis.cost`.
  - **Development checkpoints:** the tissue saved after each stage, for the GUI preview and for debugging.
- **Tests:**
  - adding a stage does not change the random numbers of the others;
  - repeat and loop semantics;
  - checkpoints reload.
- **Dependencies:** A2, A3.

**A5. Legacy compiler (backward compatibility).**
- **Deliverables:** `BodyPlan` + genome → a schedule whose stages reproduce `development.grow` exactly, including its single random stream, through a `legacy_stream=True` mode.
- **Tests:** the fingerprints of `tests/test_development.py` and of the frozen networks in dependent projects (ask the maintainer for their fixtures).
- **Dependencies:** A4.

#### Phase B: geometry and fields (parallel after A1)

**B1. Geometry primitives.**
- **Shapes:** box, sphere, ellipsoid, cylinder, sheet (2D), curved sheet (parametric surface), union / difference.
- **Operations:** `contains(points)`, `sample(n, rng)` (uniform in volume or area), `bounding_box`.
- **Local frames:** for example (u, v) along a sheet and depth w across it.
- **Tests:** sampling uniformity (statistical test); contains / sample consistency.

**B2. Meshes and voxel masks.**
- Import OBJ / STL, with point-in-mesh by ray parity and a voxel cache.
- Voxel masks from `.npz`.
- **Tests:** closed-mesh containment; voxelisation volume within 2 %.
- **Dependencies:** B1.
- **Status:** done: OBJ / STL import, ray parity vectorised over faces with a voxel cache, uniform sampling on the
  mesh volume, voxel masks from `.npz` (`VoxelMaskGeometry`). See `docs/STAGES.md`.

**B3. Scalar, vector and label fields.**
- **Field kinds:**
  - grid-based (2D / 3D arrays, trilinear sampling);
  - analytic (gradients, Gaussians, sums);
  - layer stacks: label by depth with hand-set boundaries, e.g. a "cortical layering" helper with any number of layers and thicknesses as `Param`s.
- **Tests:** sampling accuracy; layer labels match boundaries.
- **Dependencies:** B1.
- **Status:** done: grid, analytic, vector, label and layer-stack fields; layer thicknesses are `Param`s, resolved
  by `Field.bind(genome)` in `run_schedule`.

**B4. Point patterns and maps.**
- **Patterns:** point sets (blobs, centres); regular, jittered and Poisson-disc (minimum distance) generation.
- **Orientation maps:** pinwheel maps from hand-placed singularities with given charges (±1/2), or from band-pass noise. Output: a vector or angle field and the pinwheel positions.
- **Tests:** pinwheel charges recovered; minimum distances respected.
- **Dependencies:** B3.
- **Status:** done: regular, jittered and Poisson-disc point patterns (functions and the registered `PointSetField`);
  `PinwheelField` (hand-placed singularities), `pinwheel_charges`, `find_pinwheels`; band-pass maps return their
  pinwheel positions and charges.

**B5. Field operations.**
- Arithmetic, thresholding, smoothing, distance transforms (distance to a boundary or a point set), gradients.
- Everything must be usable as a `Field` param.
- **Tests:** against analytic results.
- **Dependencies:** B3.
- **Status:** done: `Add`, `Subtract`, `Multiply`, `Threshold`, `GaussianSmooth`, `DistanceTransform` (distance to a
  point set), `Gradient` and `DistanceToBoundary`, in NumPy (no SciPy); composite fields pass `bind(genome)` to their
  sub-fields.

#### Phase C: developmental stages (parallel after A4; each a registered stage with tests and cost declaration)

| Task | Stage | Content |
|---|---|---|
| C1 | `seed_cells` | Initial progenitors: by hand (a list of positions or a point set), sampled in a geometry with a density `Param` (possibly a field), or one cell. |
| C2 | `proliferate` | Symmetric or asymmetric division. Division probability per cell is a `Param` (gene, field, const). Daughter displacement; inheritance of expression with noise; lineage records; maximum cell count as a safety bound. |
| C3 | `apoptosis` | Death probability per cell (`Param`); neurotrophic variant: death when a cell gets too little input or output (after connectivity exists). |
| C4 | `migrate` | Movement along a vector field, up or down a scalar field (chemotaxis), radial or tangential, to a target layer (`Field` label), with steps and speed as `Param`s. Inside-out layering by birth order as a test case. Collisions through a cell list (O(N)). |
| C5 | `sort` / `mosaic` | Homotypic repulsion and differential adhesion (cell sorting), with a cell list (O(N)). Generalises the existing `mosaic` rule. |
| C6 | `express` | Gene-regulatory step: expression (N, K) updated by a small regulatory network (Hill-type or sigmoid, weights as `Param`s), with inputs from fields (morphogens) and from neighbours (Notch–Delta). Each step O(N K²); cell-list neighbours. |
| C7 | `differentiate` | Cell type from expression thresholds, fields, lineage or birth order (a rule table of `Param`s); sets the sign and neuron parameters per type. Generalises the existing lateral-inhibition step. |
| C8 | `grow_axons` | The existing guidance rules as a stage, on a sparse candidate search (needs D2). |
| C9 | `place_by_hand` / `connect_by_hand` | Supervised entry points: explicit cells, connection lists or connection rules from the GUI. |
| C10 | `prune` / `scale` | Remove weak synapses (threshold `Param`, optional activity criterion); homeostatic weight scaling to a target input. |
| C11 | `refine_by_activity` | A simulation phase inside development: spontaneous waves (sources and speed as `Param`s) or motor twitches with sensory feedback, a Hebbian or STDP rule, then optional pruning. Uses the spiking models; declares its simulation cost. |
| C12 | `myelinate` | Shorten delays of active or long projections (a `Param` factor). |
| C13 | `freeze` | Validate and compile the tissue to an `SNNNetwork` (end of development, or a checkpoint). |

**Status of C1–C13** (details and limits in `docs/STAGES.md`):
| Task | Status |
|---|---|
| C1 | done: count in a geometry or at one position, `density` (`Param` or field, thinning), point sets (lists, B4 patterns, `PointSetField`) |
| C2 | done: probability, displacement, lineage and `generation`, daughter type, asymmetric division, expression noise, `max_cells` bound |
| C3 | done: probability, density (cell list above N = 2048), neurotrophic in / out degree |
| C4 | done: steps along a direction (or a per-cell vector `Param`), radial, tangential, chemotaxis up / down a scalar field, stop field, boundary, target layer, `min_distance` collisions through the cell list |
| C5 | done: `sort` ranks positions along an axis by a key, differential adhesion (types × types matrix) through the cell list, and the `mosaic` stage (homotypic exclusion zones) |
| C6 | done: sigmoid or Hill step with morphogen and contact (cell-list) inputs |
| C7 | done for expression, field, lineage, birth order and position conditions (values as `Param`s) |
| C8 | partial: guidance rules as a stage; dense only up to 2048 cells per side, sparse cell-list search above (only three probability rules there, see D2); errors on missing guidance; `expression_gradient` target rule (target map from named graded expression columns, per axis gain and sign, min-max or raw levels; dense and sparse) |
| C9 | done: `place_by_hand`, `connect_by_hand` (index lists, dense matrices up to 2048 cells, connection rules by region / type / expression with probability, distance and velocity) |
| C10 | done: `prune` by weight and by coactivity (activity criterion), `scale` to a target sum with a `rate` |
| C11 | done: `lif_int` run with Poisson drivers, injected spikes, spontaneous waves or motor twitches with sensory feedback, then STDP, Hebbian or three-factor plasticity (follow with `prune`) |
| C12 | done: one factor for all delays, or for synapses selected by length, regions or presynaptic activity |
| C13 | done: `freeze` validates and compiles (optionally writes JSON / NGB1); `develop(project, genome, seed)` |

**Acceptance tests for every stage:**
- determinism;
- no-op with neutral parameters;
- the documented biological behaviour on a toy tissue (e.g. C4: inside-out order of birth vs depth);
- cost declaration within a factor 3 of measured memory and time.

#### Phase C, second wave: developmental stages C14–C24 (from a literature review)

Eleven stages proposed from the maintainer's literature review (2026-11-02). Each follows the stage
contract (`Tissue × Params × rng → Tissue`), every parameter a `Param`, neutral parameters = no-op, cost declared
O(N) or O(E) (cell list for neighbours), and a test of the documented behaviour on a toy tissue, phrased as "in this
simulation". Numbers (cycle lengths, fractions, bouton counts, ages) are `Param`s with defaults taken from the cited
sources, listed below the table.

| Task | Stage | Mechanism | What it changes | Needs |
|---|---|---|---|---|
| C14 | `cell_cycle_clock` | Cell-cycle algorithm of the ventricular epithelium (Takahashi, Nowakowski and Caviness): a fixed number of integer cycles; cycle length and its G1 phase lengthen; the fraction leaving the cycle (Q) rises while the self-renewing fraction (P = 1 − Q) falls. | Per cycle: reads `generation` (C2), sets each progenitor's exit probability Q(generation) (a `Param`: table, gene or field) and marks exiting cells postmitotic; optionally records the cycle length per generation as the birth time used by later stages. | C2; K6 (`cycling` flag) |
| C15 | `temporal_competence` | Progressive, irreversible loss of progenitor competence: early progenitors can make deep-layer and later superficial-layer neurons, older ones lose the early fates. | Updates competence expression channels as a one-way cascade (a channel can be switched off, never back on) driven by `generation` or stage index; `differentiate` conditions read them, so the same rule table gives birth-order-dependent fates. | C6, C7, C14 |
| C16 | `fasciculate` | Follower axons bundle along pioneer tracts through homophilic adhesion (e.g. L1, NCAM, Fasciclin II) and leave them where anti-adhesive cues act. | On growing arbors (N2): growth cones within a radius of a pioneer branch steer towards its direction, weighted by an adhesion code (`Param` matrix); a defasciculation field releases them. For `grow_axons` (no trajectories): followers inherit their pioneer's target offset, i.e. correlated targets. | N2, D1 |
| C17 | `choice_point` | Choice points (e.g. a midline): after crossing, guidance receptors are switched (attraction off, repulsion on), which prevents re-crossing; a new cue then steers the axon. | On N2 arbors: when a growth cone enters a choice-point geometry or field, its guidance rule set is replaced by a second one (`Param`s), and re-entry is blocked. For `grow_axons`: a projection through a waypoint region (target of leg 1 = source frame of leg 2). | N2, C8 |
| C18 | `partner_select` | Proximity is not enough (deviation from Peters' rule): synapses form only between compatible partners, through transmembrane recognition pairs (e.g. neurexin–neuroligin isoforms biasing excitatory versus inhibitory synapses). | Filters candidate synapses (from `grow_axons` or `arbor_synapses`) with a compatibility table of presynaptic × postsynaptic recognition channels (`Param` matrix, probabilities), optionally also by transmitter–receptor match (P2). Generalises the `identity_expression` rule. | P2, N3 |
| C19 | `cluster_boutons` | Connection multiplicity is cell-type specific: some connections use one or two contacts, others (e.g. basket-cell innervation) many clustered contacts per target, on specific compartments (soma, proximal dendrite, axon initial segment). | Draws a contact count per synapse from a per (pre type, post type) distribution (`Param`), stored as a per-synapse `contacts` field (weights scale with it); with morphology (N1), assigns `post_node`s on the chosen compartment labels. | K6; N1/N3 for placement |
| C20 | `gap_junction_couple` | Early circuits are coupled by gap junctions before chemical synapses mature; the coupling carries early spontaneous waves. | Creates symmetric electrical couplings between neighbouring cells (radius, probability, conductance, developmental window as `Param`s), stored apart from chemical synapses; `refine_by_activity` and the kernels simulate them. | K5 (kernels), D1 |
| C21 | `transporter_switch` | Chloride transporter switch (NKCC1 to KCC2): GABA / glycine depolarise immature neurons and hyperpolarise mature ones. | Sets each cell's chloride reversal potential (P4 conductance synapses) or its pharmacology context (P3) from its maturation: birth stage, time since birth, or accumulated activity (`Param`s). Extends the existing whole-tissue `immature` / `mature` context of the `pharmacology` stage to a per-cell, gradual switch. | P3, P4 |
| C22 | `competitive_elimination` | Synapse elimination is competitive: an input is removed when competing inputs onto the same target are active and it is not (neuromuscular junction, climbing fibres), not by disuse alone. | From the activity recorded in `refine_by_activity`: for each target, inputs whose activity (or correlation with the target) falls below a fraction of their strongest competitor lose weight or are removed. O(E) grouped by target. Differs from `prune` (absolute weight or coactivity). | C11 |
| C23 | `critical_period_gate` | Critical-period closure: maturation of inhibitory (e.g. parvalbumin-expressing) cells and perineuronal nets stabilise synapses and stop large-scale plasticity. | Sets a per-cell or per-synapse `plastic` mask from a maturation criterion (`Param`s: a cell type's maturity, stage index, a field); `refine_by_activity`, `prune`, `scale` and later structural stages skip masked synapses. | K6, C10, C11 |
| C24 | `tissue_expand` | Tissue grows: proliferation pushes existing cells, layers and tracts outward instead of crowding a fixed box (as in growth simulators such as Cx3D and NeuroDevSim). | Moves cells by a displacement field (radial, anisotropic or a vector `Field`) or by local volume pressure from cell density (cell list), and updates region boxes and geometries. Synapses keep their delays unless asked to recompute them from the new distances (a `Param`). | B1–B5, D1 |

**Contract changes needed (each a dedicated PR with versioned docs, as K1–K4):**
- **K5. Electrical synapses in the kernels.** A per-neuron coupling current `g · (v_j − v_i)` over a symmetric coupling
  list, in all four kernels (Python, JAX, browser, nglif) with equivalence tests, in NGB1 as an extension table, and in
  the cost model. Integer form to be defined (e.g. `(g · (v_j − v_i)) >> shift_g`), deterministic.
- **K6. Optional Tissue fields.** `cycling` (bool, C14), `plastic` (per synapse, C23) and `contacts` (per synapse, int,
  C19), defaulted so that existing code and networks are unchanged; carried by add / compact / subset / concatenate and
  into the network where they matter (contacts as weights, plastic for later plasticity).
  - **Status:** partial: `Tissue.cycling` (bool, per cell; `True` for `seed_cells` / `proliferate`, `False` for
    `place_by_hand`; carried by `add_cells` / `compact` / `subset` / `concatenate` / `to_network` / `from_network`;
    `proliferate` divides only cycling cells) done; `SparseConnectivity.plastic` (bool, per synapse; None = all
    plastic; carried by `take` / `concat` / `append` / `compact` / `subset` / `concatenate` and, in memory only, by
    `to_network` / `from_network`; `refine_by_activity`, `prune`, `scale` and `neuromodulate` leave non-plastic synapses
    untouched) done; `contacts` (C19) done.

**Status of C14–C24:**
| Task | Status |
|---|---|
| C14 | done: `cell_cycle_clock` (#68), Q(generation) as a power law or table, cycle-length table, `Tissue.cycling` (K6 part, #67) |
| C15 | done: `temporal_competence` (#69), one-way competence channels advanced by generation or a field, fixed at cycle exit |
| C21 | done: `transporter_switch` (#74), per-cell chloride reversal potential from maturation (NKCC1 → KCC2 switch) |
| C22 | done: `competitive_elimination` in `neurogenesis/stages/competition.py`, coactivity with the target versus a `fraction` of the strongest competitor, weaken by `delta` or remove; silent targets and non-plastic synapses unchanged; raster from `spikes` or `refine_by_activity` with `record` |
| C23 | done: `critical_period_gate`, sets the K6 `plastic` mask (#73): opens on inhibitory maturity from a source cell type (or a maturity `Param`, or explicitly), closes after a number of gate calls, a maturity threshold or explicitly; `reopen`; mouse / cat ages in `examples/profiles/critical_period_gate.json` |
| C16 | done: `fasciculate` in `neurogenesis/stages/fasciculation.py`, arbor tangent steering via `CellList` & adhesion matrix |
| C17 | done: `choice_point` in `neurogenesis/stages/fasciculation.py`, receptor switch in geometry/field & waypoint routing |
| C19 | partial: `contacts` and weight scaling done; `post_node` placement waits for N2/N3 |
| C24 | done: `tissue_expand` in `neurogenesis/stages/expansion.py`, dilation by `1 + kappa * N_new / N_total` about a centre along chosen axes; optional delay recompute |
| C18, C20 | not done |

**Order and priorities.** C14 → C15 first (they make birth-order-dependent layering emerge in G2.3, instead of being
imposed); C21, C22, C23 next (they use existing P3 / P4 and C11); C18 and C24 independent; C16, C17, C19 after N2 / N3;
C20 deferred (interim approximation below; K5 later).

**Defaults and sources** (from the maintainer's literature review, 2026-11-02; each default is a `Param` that a gene,
field or fit can replace; values are about the cited species and ages, and results are "in this simulation"):

- **C14 `cell_cycle_clock`** — mouse neocortex, neuronogenetic interval E11–E17 (Takahashi, Nowakowski and Caviness
  1995, J Neurosci 15:6046–6057; 1996, J Neurosci 16:6183–6196; Caviness, Takahashi and Nowakowski 2000, in Nelson and
  Luciana, eds., Handbook of Developmental Cognitive Neuroscience, MIT Press, pp. 7–22).
  - 11 integer cycles (CC1–CC11).
  - Cycle length T_C from 8.1 h (CC1, E11) to 18.4 h (CC11); T_G2+M constant at 2 h and T_S about 4 h without a trend;
    the lengthening is almost entirely G1 (3.2 h → 12.4 h, fourfold; Takahashi et al. 1995, Table 1).
  - Leaving fraction `Q(CC) = min(1, k · CC^a)`, defaults `k = 0.009`, `a = 1.97`; P = 1 − Q.
  - Reference table (one founder; `PVE_n = 2 · PVE_(n−1) · P_(n−1)` progenitors, `OUT_n = 2 · PVE_n · Q_n` neurons):

    | CC | age | T_C (h) | T_G1 (h) | Q | PVE | OUT | cumulative |
    |---|---|---|---|---|---|---|---|
    | 1 | E11.0 | 8.1 | 3.2 | 0.009 | 1.00 | 0.02 | 0.02 |
    | 2 | E11.3 | 8.8 | 3.2 | 0.035 | 1.98 | 0.14 | 0.16 |
    | 3 | E11.7 | 9.6 | 3.3 | 0.078 | 3.82 | 0.60 | 0.76 |
    | 4 | E12.1 | 10.3 | 3.5 | 0.138 | 7.05 | 1.95 | 2.70 |
    | 5 | E12.5 | 10.8 | 4.5 | 0.214 | 12.15 | 5.21 | 7.91 |
    | 6 | E13.0 | 11.4 | 5.5 | 0.307 | 19.09 | 11.72 | 19.64 |
    | 7 | E13.5 | 13.1 | 7.3 | 0.416 | 26.46 | 22.01 | 41.65 |
    | 8 | E14.0 | 15.1 | 9.3 | 0.541 | 30.90 | 33.45 | 75.10 |
    | 9 | E14.6 | 16.6 | 10.9 | 0.683 | 28.36 | 38.71 | 113.81 |
    | 10 | E15.3 | 17.8 | 12.0 | 0.840 | 18.01 | 30.25 | 144.06 |
    | 11 | E16.1 | 18.4 | 12.4 | 1.000 | 5.77 | 11.53 | 155.59 |

    Q and the counts follow from the formula (checked here). T_C and T_G1 were measured per embryonic day, E11–E16
    (Takahashi et al. 1995, Table 1: T_C 8.1, 10.2, 11.4, 15.1, 17.5, 18.4 h); the per-cycle values here are read from
    that table by linear interpolation at the start of each cycle, so they are defaults derived from data, not
    measurements. The age column is the age at the start of the cycle; the 11 cycles take 140 h (about 5.8 days).
  - Q passes 0.5 during CC8 (E14), when the progenitor pool peaks (about 31 founders' worth); about 27 % of the
    neurons are made in CC1–CC7. Without cell death a founder yields about 156 neurons; the reported mean of about 140
    corresponds to a basal progenitor death of about 1 % per cycle (the review's figure; 0.99^11 × 156 ≈ 140).
  - Acceptance tests: Q per cycle follows the formula; the first cycle with Q > 0.5 is CC8; the progenitor pool peaks
    at CC8; neurons per founder ≈ 156 with no death and ≈ 140 with 1 % death per cycle (averaged over development
    seeds); division times follow T_C(CC).
  - **Status:** done: `cell_cycle_clock` in `neurogenesis/stages/population.py`, `Tissue.birth_time`, reference profile
    `examples/profiles/cell_cycle_clock.json`, and 25-seed acceptance tests in `tests/test_stages_cell_cycle.py`.
- **C15 `temporal_competence`** — ferret neocortex (McConnell and Kaznowski 1991, Science 254:282–285; McConnell 1995,
  Neuron 15:761–768; for the invertebrate temporal cascade, Isshiki et al. 2001, Cell 106:511–521).
  - Birth order to layer: layer VI E30–E34, layer V E35–E38, layer IV E39–P0, layers II/III P1–P3.
  - Rules the stage must reproduce, from heterochronic transplantation: postmitotic neurons keep their fate; young
    progenitors transplanted in S phase into an old host adopt the host's (later) fate; old progenitors in a young host
    do not regain early fates. Hence: competence moves one way only, it can be advanced by the environment (a `Param`
    field), never reversed, and it is fixed at cell-cycle exit.
  - Acceptance test: the three transplantation outcomes, simulated as moving cells between tissues of different
    competence stages.
  - **Status:** done: `temporal_competence` in `neurogenesis/stages/differentiation.py`, reference profile
    `examples/profiles/temporal_competence.json`, and heterochronic transplantation tests in
    `tests/test_stages_temporal_competence.py`.
- **C19 `cluster_boutons`** — neocortex (cat, rat, macaque, human; DeFelipe 2002; Douglas and Martin 2004 and
  Shepherd and Grillner, eds., 2010, Handbook of Brain Microcircuits; Somogyi et al. 1982, 1983; Peters 1984; Shepherd,
  ed., 2004, The Synaptic Organization of the Brain, ch. 12).
  - Placement (with morphology): pyramidal → pyramidal on distal spines (not soma or axon initial segment); basket-cell
    boutons about one third each on spines, proximal dendritic shafts and soma; chandelier cartridges (up to about 40
    symmetric synapses) only on the axon initial segment; 3–5 chandelier cells per superficial-layer pyramidal axon
    initial segment; one chandelier cell contacts about 300 pyramidal neurons within 200–400 µm.
  - Contacts per connection (multiplicity of a unitary connection, distinct from convergence), defaults as
    distributions per (pre type, post type) `Param`:
    - pyramidal → pyramidal: 3–8 contacts (mean about 5–6; extremes 3–12) on dendritic spines ("3–12 synapses make up
      glutamatergic connections", Markram, DeFelipe and Thomson, in Shepherd and Grillner 2010, ch. 3; paired recordings
      with reconstruction: Markram et al. 1997, J Physiol 500:409–440, layer 5; Feldmeyer et al., layers 2/3 and 4);
    - basket → pyramidal: 5–15 contacts (mean about 10; "about 10 synapses on average with each target", Shepherd 2004,
      ch. 12, p. 520), split about 30 % soma, 35 % proximal dendrites, 35 % spines; convergence of 10–30 basket cells
      per soma is a separate number (it follows from connection probabilities, not from this stage);
    - chandelier → pyramidal: 8–14 contacts per cartridge on the axon initial segment, so that the 3–5 converging
      chandelier cells give the total of about 40 axon-initial-segment synapses of a layer 2/3 pyramidal cell (about
      12–15 for layer 5) (Shepherd 2004, ch. 12, p. 521; Somogyi et al. 1982; Peters 1984).
  - Acceptance tests: contact counts per type pair follow their distributions; with morphology, the compartment
    fractions match; in a layer 2/3 test tissue the total chandelier contacts per axon initial segment is about 40.
- **C21 `transporter_switch`** — rat and mouse (Rivera et al. 1999, Nature 397:251–255; Ben-Ari 2002, Nat Rev Neurosci
  3:728–739; retina: Vu et al. 2000).
  - Immature: high NKCC1, [Cl⁻]i about 25–40 mM, E_Cl about −50 to −40 mV (above a rest of −65 to −70 mV): GABA
    depolarises. Mature: KCC2, [Cl⁻]i about 4–5 mM, E_Cl about −75 mV: GABA hyperpolarises.
  - Timing: NKCC1-dominated from E12 to about P4; functional switch between P5 and P10 (hippocampus and neocortex), up
    to P12–P14 in superficial layers.
  - Mechanism: onset programmed by age, timing accelerated by activity (GABA-driven depolarisation → Ca²⁺ → BDNF /
    TrkB → KCC2). Default rule: E_Cl(cell) moves from E_Cl,immature to E_Cl,mature with the cell's age (a sigmoid with
    midpoint and width as `Param`s), and its accumulated activity shifts the midpoint earlier (gain `Param`, 0 = purely
    programmed).
  - **Status:** done: `transporter_switch` in `neurogenesis/stages/pharmacology.py` (per-synapse `e_rev` from the
    postsynaptic cell's E_Cl; signed weights for `lif_int` with `v_rest`), reference profile
    `examples/profiles/transporter_switch.json`, tests in `tests/test_stages_transporter_switch.py`. Activity is a
    `Param` (`refine_by_activity` does not record per-cell activity yet).
- **C22 `competitive_elimination`** — neonatal rat and mouse neuromuscular junction, P0 (poly-innervated) to P14
  (single input), and climbing fibres on Purkinje cells (P0–P20) (Willshaw 1981, Proc R Soc Lond B 212:401–419; Gouzé,
  Lasry and Changeux 1983, Biol Cybern 46:207–215; Balice-Gordon and Lichtman 1994, Nature 372:519–524).
  - Rule: an input is weakened or removed only when a competing input on the same target drives it above threshold
    while it is inactive or asynchronous; if the target is silent (no suprathreshold activity), nothing is eliminated.
  - Acceptance test: silent target → no elimination; one active input + inactive ones → the inactive ones are removed;
    synchronous inputs → both kept.
- **C23 `critical_period_gate`** — ocular dominance in V1 (Hensch 2005, Nat Rev Neurosci 6:877–888; Pizzorusso et al.
  2002, Science 298:1248–1251; Hubel and Wiesel 1970, J Physiol 206:419–436).
  - Mouse: opens at P19–P21 (about a week after eye opening at P13–P14), peaks at P26–P28, closes P32–P35. Cat: opens
    in weeks 3–4, peaks around week 6, closes by about 3 months.
  - Opening when parvalbumin-cell inhibition reaches a threshold (a `Param` on that type's mean inhibitory weight or
    maturity); closing by perineuronal-net condensation around parvalbumin cells (and myelin-associated factors),
    which the `plastic` mask represents; removing the mask (as chondroitinase ABC does) reopens plasticity.
- **C24 `tissue_expand`** — mouse, CC1–CC8 (E11–E15): ventricular epithelium volume × about 50, surface × about 25
  (about × 5 per tangential dimension) (Caviness, Takahashi and Nowakowski 2000). Growth simulators (Zubler and Douglas
  2009, Front Comput Neurosci 3:25; De Schutter 2023, Front Neuroinform 17:1212384) move cells with overdamped
  mechanics (`γ dx/dt = Σ repulsion + adhesion`, repulsion `k · max(0, R_i + R_j − |x_i − x_j|)`); too costly here.
  Default rule instead: after each proliferation step, dilate positions about a centre (or along chosen axes)
  by `1 + κ · N_new / N_total`, with κ and the axes as `Param`s; the existing `min_distance` collisions of `migrate`
  (cell list) handle local overlaps. Acceptance test: with κ chosen to give × 5 per tangential axis over CC1–CC8, the
  cell density stays bounded while the count grows.
- **C20 `gap_junction_couple`** — deferred. Bidirectional, instantaneous coupling (`I = g (V_i − V_j)`) does not fit
  the kernels' causal, delayed event scheme without an implicit solver or substeps, which would cost an order of
  magnitude and touch all four bit-identical kernels (K5). Interim approximation, no kernel change: reciprocal
  excitatory chemical synapses of delay 0 (first effect after one tick) between neighbouring cells within a
  developmental window. This passes spikes, not subthreshold currents: it can carry early synchronous waves, but it is
  not an electrical synapse, and the docs must say so. K5 stays on the roadmap for later.

#### Phase D: performance

**D1. Cell lists / spatial hash.**
- Shared neighbour search: radius queries and k-nearest, in NumPy, with optional JAX.
- **Tests:** exact against brute force on small sets; O(N) scaling measured.
- **Status:** done: `CellList` radius and k-nearest queries, vectorised in NumPy (#45) with chunked memory and a
  declared cost, plus an optional `backend="jax"` path (float64 on the default JAX device, same indices as NumPy,
  distances within floating-point tolerance, plain NumPy arrays returned; no speed-up: about 1000 times slower than
  NumPy on a CPU, GPU not measured; `backend="auto"` keeps NumPy on a CPU and times both once on an accelerator).

**D2. Sparse guidance.**
- Candidate targets only within c·σ of the target point (cell lists), then the same probabilities as the dense version.
- **Must reproduce** the dense version exactly when the cutoff covers all candidates (`exact=True` mode, for backward compatibility).
- **Then** the default for large tissues.
- **Tests:** equality in exact mode; memory O(E) for 10⁵ cells.
- **Dependencies:** D1.
- **Status:** partial: `sparse_guidance` with an exact (dense) mode and a cell-list mode supporting three probability
  rules (`identity_expression`, `no_autapse`, `source_sign`); `grow_axons` uses the cell-list mode by default above
  2048 cells per side (#44). The sparse mode draws different random numbers from the dense one, and the legacy `grow()`
  stays dense (fingerprints).

**D3. Development cost model.**
- Per-stage cost declarations summed by `neurogenesis.cost`, with machine calibration as for simulations.
- **Command line:** `python -m neurogenesis estimate --project p.json`.
- **Dependencies:** A4.
- **Status:** done: `neurogenesis.devcost` walks the schedule without running it, predicts cells and synapses per stage
  (worst case for `Gene` / `Fit` params), sums the declared costs, calibrates seconds per declared op once per machine,
  and `check_development` refuses schedules whose peak memory does not fit. Synapse predictions of rules and guidance
  are upper estimates.

**D4. Chunked and streaming growth.**
- Tissues larger than memory processed in spatial chunks, writing synapses to disk (`.npz` shards).
- **Dependencies:** D1, D2.
- **Status:** done: `neurogenesis.streaming` (`grow_axons_streaming`, `SynapseShardManifest`, `assemble_shards`, `stream_stats`, `streaming_growth_cost`).

**D5. Performance regression CI.**
- `scripts/benchmark_performance.py` runs fixed CPU simulation and cell-list workloads. Each main-branch run stores its timings and machine fingerprint as a CI artifact and reports regressions above 30 % against the previous run when the machine matches (report only: shared runners vary too much for a gate). Locally, `--baseline perf.json --enforce` makes a regression fail on the machine that recorded the baseline.

#### Fix wave (F1–F6, review fixes of Wave 2; not the Phase F viewer tasks)
- **F1** field and geometry instantiation: done (#28).
- **F2** remove `eval`: done (#29, #44): fields, conditions, `Expr` and `CurvedSheet` use the restricted evaluator.
- **F3** guidance through `Param`s, `"src->dst"` keys: done (#30); stricter errors in #38.
- **F4** low compute: done (#31, #44): apoptosis uses the cell list; `grow_axons` builds dense Ns × Nd arrays only up
  to 2048 cells per side (or with `exact=True` up to 2^24 pairs) and uses the sparse cell-list search above.
- **F5** delay limit of 31 ticks: done (#35).
- **F6** `refine_by_activity` correctness and sign preservation: done (#37).

#### Phase E: learning and optimisation

**E1. Plasticity component.**
- STDP, Hebbian with normalisation, and three-factor (eligibility × modulator) rules, usable during simulation and in C11.
- Integer-friendly versions for `lif_int`.
- **Tests:** classic STDP window shape; weight bounds; determinism.
- **Status:** done: `STDPRule`, `HebbianRule` (normalised) and `ThreeFactorRule` in `neurogenesis.spiking.plasticity`,
  with integer traces for `lif_int`; used by `refine_by_activity` (C11).

**E2. Supervised fitting of `Fit` params.**
- Targets: cell density per region, layer thickness, connection probability vs distance, receptive-field metrics, firing rates.
- Optimisers: CMA-ES or another gradient-free method (default, robust to stochastic development); JAX gradients where a stage is differentiable.
- Multiple development seeds per evaluation (robustness).
- **Dependencies:** A3, A4.
- **Status:** done: `neurogenesis.fitting` (fitted values in `genome["__fit__"]`, metric registry, CMA-ES, multi-seed
  loss, `python -m neurogenesis fit`). Not done: JAX gradients, receptive-field metrics (add them as registered metrics).

**E3. Semi-supervised workflows.**
- Evolution of `Gene` params with `Fit` params refitted in an inner loop, or fixed.
- Multi-objective (Pareto) option.
- **Dependencies:** E2.
- **Status:** done: `neurogenesis.evolution.semi_supervised` (`inner_fit_evaluator` with refit or fixed `Fit` params,
  `evolve_pareto`, `pareto_front`).

**E4. Validation harness.**
- A declarative assay description (stimuli, readout, metric, gate), stored in modules and rerun by `neurogenesis validate module.json`.
- Knockout assays included.
- **Status:** done: `neurogenesis.validation` and `python -m neurogenesis validate module.json` (stimuli per region,
  rate or fraction-active readout, metrics, gates, pass fraction, knockouts).

#### Phase F: Browser Viewer & Web Demo (viz/, TypeScript + three.js)

`viz/` remains the lightweight browser viewer, the reference browser kernel implementation (`kernel.ts`), and the interactive web demo. Full-featured editors move to the native desktop apps (Phase H), while non-UI infrastructure (command protocols, schemas, and streaming) is exposed as shared core contracts.

**F6. Network view enhancements [done].** Viewer colour modes (region, role, sign, mode, compartment) and population spike raster filtering.

**F7. Bridge streaming.** Local streaming (`neurogenesis/bridge/`) of development checkpoints and live simulation activity over standard protocols.

#### Phase H: Native Desktop Applications (macOS, Windows, Linux)

Native desktop applications with zero developer setup required for end users:
- macOS app: SwiftUI in `desktop/mac/` (built by the macOS agent).
- Windows & Linux shared app: Avalonia (C# / .NET 8) in `desktop/avalonia/` (built by the Avalonia agent on a Windows machine).
- Both apps consume the portable C simulation kernel (`nglif`), communicate with the embedded Python runtime via the shared bridge (`neurogenesis/bridge/`), render dynamic UI panels generated from the UI schema, and style components per `visual_spec.json`.

- **Ownership & Isolation:**
  - `desktop/mac/` is owned strictly by the macOS agent.
  - `desktop/avalonia/` is owned strictly by the Avalonia agent.
  - Jules agents never edit `desktop/mac/` or `desktop/avalonia/`.
  - `desktop/common/` and `neurogenesis/bridge/` are shared contracts (protocol, NGB1 binary network format, UI schema, visual spec, C kernel `nglif`, golden fixtures, runtime scripts).
  - No app may duplicate shared logic. Everything about stages, Params, models, and genes is driven by the bridge and the UI schema. Visual look is specified by `visual_spec.json`. Simulation uses `nglif`.

- **Tasks:**
  - **H1. Bridge in the package [contract].** (`neurogenesis/bridge/`, protocol spec `desktop/common/PROTOCOL.md`, automated tests) — *by the Mac agent*.
  - **H2. Portable C kernel `nglif` + NGB1 I/O [contract].** (`desktop/common/native/nglif/`, golden fixtures `fixtures/`, 3-OS CI matrix) — *by the Mac agent*.
  - **H3. UI schema export from Python [contract, done].** Versioned JSON export from Python (`python -m neurogenesis ui-schema` and bridge `ui_schema` via `neurogenesis.project.ui_schema.export_ui_schema`): stage registry (Params, kinds, bounds, docs, cost samples), models with cost estimates, guidance/placement rules, genes of a schedule (`collect_genes`), Project schema.
  - **H4. Embedded Python runtime scripts.** Automated packaging scripts using `python-build-standalone` on all three OSes (macOS, Windows, and Linux, x86-64 and arm64).
  - **H5. Windows + Linux application (Avalonia).** (`desktop/avalonia/`, C# / .NET 8 cross-platform UI), consuming `nglif` through P/Invoke C ABI and the Python bridge via JSON-RPC/stdio protocol, dynamic panels generated from UI schema, colors from `visual_spec.json`. Packaging: Windows setup `.exe` + `.zip`, Linux AppImage. Built by the Avalonia agent.
  - **H6. Linux packaging extras (optional / later).** Flatpak manifest and distribution testing.
  - **H7. Release workflow.** GitHub Actions workflow triggered on version tags: builds and bundles macOS DMG (`desktop/mac/`), Windows setup/zip and Linux AppImage (`desktop/avalonia/`); smoke-tests bridge connection and kernel fixtures across all three operating systems.

#### Pre-requisite Contract Changes: Connectomes, Pharmacology & Scale

Before implementing Phase I, P, and N, core contracts are updated in dedicated, isolated tasks:

**K1. Project schema extension (contract bump 1.0.0 -> 1.1.0).**
- Add optional top-level sections to Project schema: `data_sources` (dataset id, files, filters), `pharmacology` (profile reference, channel properties), and `morphology` (compartment models, dendritic arbor settings).
- Backward compatibility: schema version bump to `1.1.0` with explicit migration function `migrate_100_to_110()` in `neurogenesis/project/schema.py` ensuring all 1.0.0 projects load and execute identically without modification.
- Dedicated files: `neurogenesis/project/schema.py`, `neurogenesis/project/io.py`, `tests/test_project.py`.
- **Status:** done: `SCHEMA_VERSION = "1.1.0"`, optional sections validated, `migrate_100_to_110`, chained migrations,
  tests in `tests/test_project_schema.py`.

**K2. Tissue struct-of-arrays extensions (contract).**
- Extend `Tissue` with optional/defaulted arrays:
  - `transmitter`: `(N,)` `int8` bitmask or small list (co-transmission support; default 0 = undefined/classic sign).
  - `receptor_expression`: `(N, R)` `float32` (expression levels per receptor type, default empty/neutral).
  - `morphology`: optional reference to `ArborStore` (node arrays, branch parent indices, compartment types).
  - Synapse connectivity arrays extended with:
    - `receptor_class`: `(E,)` `uint8`;
    - optional morphology fields: `pre_node` `(E,)` `int32`, `post_node` `(E,)` `int32`, `path_length` `(E,)` `float32`, `dendritic_distance` `(E,)` `float32`.
- Preserves float64 positions, `clone_id` lineage tracking, and exact bit-for-bit output on existing stages and legacy plans.
- **Status:** done: `Tissue.transmitter`, `receptor_expression`, `morphology`; `SparseConnectivity.receptor_class` and
  the optional morphology arrays, kept aligned by `take` / `concat` / `append` in every stage; fingerprints unchanged.

**K3. Array-backed synapse store & uint32 neuron limit (scale invariant).**
- Lift neuron indexing from `uint16` to `uint32` across all network representations, CSR indices, NGB1 binary format, and spiking kernels (Python, JAX, browser TypeScript, and portable C `nglif`).
- Replace `SNNNetwork` Python list of `SynapseData` objects with a vectorized struct-of-arrays synapse store (`pre`, `post`, `weight`, `delay`, and per-synapse fields) for memory scalability ($O(E)$ memory, handling 10M–150M synapses).
- Vectorized `to_csr()` via `argsort(pre)` with a thin compatibility list view.
- Disallow JSON for networks of this scale; stream to `.npz` or NGB1.
- Synchronized after Mac agent's H2 merges (aligning NGB1 and `nglif` C kernel on uint32).
- **Status:** done: `SNNNetwork.from_arrays`, `_SynapseListView`, vectorized `to_csr` with `uint32` indices, `save_npz` / `load_npz`, `save_json` scale guard, and `uint32` `colIdx` in Python, JAX, browser (`viz/src/sim/types.ts`, `memory.ts`), and C (`nglif`).

**K4. Multi-word spike history for extended conduction delays (kernel contract).**
- Generalize spike history from 1 word (max 31 ticks) to `ceil((max_delay + 1) / 32)` 32-bit words per neuron across all four kernels (Python, JAX, browser `kernel.ts`, and C `nglif`).
- Bit-for-bit equivalence across all four kernels with memory overhead declared in `neurogenesis.cost`.
- Until K4 merges, tasks N3 and I3 must clip conduction delays exceeding 31 ms with a counted warning (never silently).
- **Status:** done: multi-word spike history ($W = \lceil(\text{max\_delay} + 1) / 32\rceil$ 32-bit words) across Python `reference.py`, JAX `jax_backend.py` & `models/base.py`, browser `viz/src/sim/kernel.ts`, and C `nglif.c`, with memory overhead declared in `neurogenesis.cost`.

**K4b. Longer delays in the developmental stages.** The kernels accept delays beyond 31 ticks (K4), but `grow_axons`,
`connect_by_hand`, `myelinate` and `dendritic_attenuate` still clip computed delays at 31. Replace the hard-coded bound
with the `max_delay` Param (default: no clip, or a documented bound tied to the cost model), warn only when a
user-set bound clips, and test that a long projection keeps its delay and simulates identically in the four kernels.
- **Status:** partial (#72): `grow_axons` (and its streamed variant), `connect_by_hand`, `myelinate`,
  `dendritic_attenuate` and `freeze` use the `max_delay` Param, default the storage bound of 255 ticks
  (`MAX_DELAY_TICKS`), and warn only when clipping. Still clipped at 31: `arbor_synapses`
  (`neurogenesis/stages/arbors.py`) and the connectome importer (`neurogenesis/data/importer.py`).

#### Phase I: Connectome Data Layer

Enable importing public connectome datasets (supervised) and using them as statistical targets for developmental fitting (semi-supervised), without hardcoding species or dataset names in `neurogenesis/` core. Optional dependency `pip install -e ".[data]"` (lazy `pyarrow` import).

**I1. Connectome dataset downloader & catalogue.**
- Generic dataset manifest format (`datasets/<id>.json`): dataset URL, file checksums, license, format specifications (Feather, Parquet, CSV, SWC), column mappings, physical unit metadata, and citations.
- CLI command: `python -m neurogenesis data fetch <dataset_id> --target <dir>`.
- Prints dataset size and license before download; caches in `~/.cache/neurogenesis/data` outside repository tree. Resumable, verified against SHA256 checksums.
- Unit tests run against tiny synthetic fixtures created in-memory, never downloading over the network.
- **Status:** done: `neurogenesis.data.catalogue` (manifests, `fetch` with Range resume and SHA-256) and `python -m neurogenesis data info|fetch`.

**I2. Streaming connectome importer.**
- Memory-efficient streaming import via lazy `pyarrow`:
  - Stream neuron annotations (soma coordinates, predicted transmitters) using manifest column mappings.
  - Stream edge tables in record batches filtered with `pyarrow.compute.is_in(pre, post)` directly into sparse connectivity ($O(\text{selected edges})$ memory).
  - Filter criteria: cell type (list / regex), class / superclass, region / ROI, side, explicit ID list, bounding box, minimum synapse count.
  - Print cost estimate before allocating network.
  - Record license and citation metadata in the provenance of created `Tissue` / network.
- **Status:** done: `neurogenesis.data.import_connectome` (CSV with NumPy, Parquet / Feather / CSV with optional pyarrow; filters by type (list or regex), class, superclass, region, side, ids, bounding box, minimum synapses; provenance in `info`).

**I3. Coordinate frames & spatial registration.**
- Map raw voxel coordinates to physical units ($\mu\text{m}$) via affine transform declared in manifest (voxel size, origin offset, optional mirroring).
- Space units set conduction delays; region-local $[0, 1]^3$ coordinates are derived only inside regions.
- Neuropil ROI masks and column coordinates interfaced via `Geometry` and `Field` adapters.
- Clip delays $>31\text{ ms}$ with a counted warning until K4 merges.
- **Status:** done: affine registration (voxel size, origin, mirroring) and clipped delays; ROI masks and column coordinates interfaced via `Geometry` and `Field` adapters (`neurogenesis.data.registration`), with local tests in PR ([skip ci]).

**I4. Synapse count to synaptic weight mapping.**
- Configurable mapping from anatomical synapse counts to functional weights (linear, Hill/saturating, or `Param`).
- Weight mapping fits into target int16 weight range (`w_clip` 512–2047).
- Depends on P1: sign and channel determination compiled through pharmacology profile. (Before P1 exists, accepts an explicit caller-provided transmitter $\to$ sign map).
- **Status:** done: `map_weights` (linear, Hill, log; `Param` values; `w_clip` 512–2047), signs from a transmitter map, transmitter bits from a profile.

**I5. Cell-type statistical targets for fitting.**
- Extract cell-type-level statistical targets for Phase E (`E2` / `E3` fitting):
  - Type $\times$ type synapse matrix, counts per type, degree and weight distributions, transmitter composition, distance dependence;
  - Topographical column fraction (inputs from same vs neighboring columns from B3/B4 field);
  - Spread across individuals/datasets sets fitting tolerance; metrics averaged over development seeds.
- Depends on E2 (`Fit` params).
- **Status:** done: `neurogenesis.data.targets` (`extract_fitting_targets`, `compare_type_statistics`, `mean_weight` and `column_fraction` metrics registered in `neurogenesis.fitting`).

**I6. Optic-lobe column benchmark example.**
- Supervised baseline: import verified subcircuit from local attributed CC-BY 4.0 test fixture (e.g. 14k-neuron visual circuit subset: Mi1, Tm3, Mi4, Mi9 $\to$ T4) and simulate.
- Grown circuit: semi-supervised development where hexagonal columns are a hand-drawn field, cell identities assigned, and developmental parameters fitted via I5/E2 to match imported type matrix and topography.
- Direct statistical comparison between grown and imported networks. Runnable on CPU in minutes.
- Note: Direction selectivity depends on dendritic placement and timing (evaluated post N3/N5), not point-neuron somata.
- **Status:** done: `examples/optic_lobe_column.py` and `tests/test_optic_lobe_column.py`.

#### Phase P: Transmitter & Receptor Pharmacology

Transmitter action depends on receptors and developmental context, never a static fixed sign (+1 / -1) per transmitter.

**P1. Pharmacology profiles as data.**
- Profiles in `profiles/<profile_id>.json` mapping neurotransmitter IDs and receptor classes to post-synaptic reversal potentials, kinetics, and conductance parameters.
- Default profile reproduces today's per-neuron sign bit-for-bit (legacy fingerprints and A5 fingerprints unchanged); re-expresses existing `receptor_sign` rule (`gd["receptor"] = "inverting"`).
- Profile entries parameterizable via `Param` (const, gene, field, expr).
- Example documented profile entries with biological citations (e.g. GluCl, mGluR6-like, histamine-gated Cl-, GABA-A/glycine chloride equilibrium switches; Ben-Ari 2002 Nat Rev Neurosci).
- Support co-transmission: presynaptic neuron emits multiple transmitters. Dale's principle is optional.
- **Status:** done: `neurogenesis.pharmacology` (profiles, default profile bit-for-bit, `compile_weights`, legacy `receptor_sign` re-expressed), example profile in `examples/profiles/`.

**P2. Receptor expression & assignment.**
- Cell-type-specific or field-dependent receptor expression profiles (`Tissue.receptor_expression`).
- Post-synaptic receptor type assignment during synaptogenesis based on transmitter-receptor compatibility rules.
- **Status:** done: `set_transmitters`, `express_receptors`, `assign_receptors` stages.

**P3. Developmental switches & context compilation.**
- Model developmental shifts in reversal potentials (e.g. GABA depolarizing early $\to$ hyperpolarizing later).
- Compile step maps (transmitter, receptor class, developmental context) into signed integer weight and channel assignment for `lif_int`, or conductance reversal potential for float models.
- Operates both during development (inside C11 `refine_by_activity`) and during network compilation.
- **Status:** done: `pharmacology` stage (`compile_weights` and `compile_conductances`) compiles signed integer weights and conductance reversal potentials / decay time constants per context.

**P4. Multi-channel & conductance-based spiking kernels (post H2, K3, K4).**
- Integer LIF: $k$ synaptic current channels with distinct decay shifts, bit-for-bit equivalent across all four kernels.
- Float models (`two_compartment`, `hh`): conductance-based synapses with reversal potentials, equivalent within floating-point tolerance.
- **Status:** done: multi-channel synaptic currents in `lif_int` and conductance-based reversal potential synapses (`e_rev`, `tau_decay`) in `two_compartment` and `hh`.

**P5. Neuromodulation & volume transmission.**
- Diffuse neuromodulatory fields acting on plasticity eligibility traces (interfacing Phase E `ThreeFactorRule`).
- **Status:** done: `neurogenesis.neuromodulation` (`VolumeModulator`, `apply_modulated_plasticity`, `neuromodulate` stage; every parameter a `Param`, see `docs/STAGES.md`).

#### Phase N: Branching Neurites & Arbor Morphology

Grow realistic arbor morphologies and calculate conduction delays based on neurite path length rather than Euclidean soma distance.

**N1. ArborStore morphology representation & SWC I/O.**
- Low-memory struct-of-arrays representation of tree morphologies (`ArborStore`): node coordinates, radii, parent indices, compartment types.
- Generic compartment labels: soma, axon, dendrite, neurite + user labels (supporting unipolar invertebrate architectures without enforcing apical/basal).
- SWC format reader, downsampler, and writer.
- **Status:** done: `neurogenesis.arbors` (`ArborStore`, `read_swc`, `write_swc`, `downsample`, `to_budget`, path lengths).

**N2. `grow_arbors` developmental stage.**
- Stochastic branching stage: branching probability, elongation, branching angle, stop/target field rules, retraction.
- Every parameter is a `Param`; draws randomness strictly from stage `rng`.
- Cost declared and verified within factor 3.
- **Status:** done: `grow_arbors` stage (`docs/STAGES.md`).

**N3. Arbor synaptogenesis & path-length conduction delays.**
- Geometric proximity search between axons and dendrites using D1 `CellList` in $O(\text{nodes})$.
- Conduction delay calculated from path length along neurites + axonal velocity.
- Clips delays $>31\text{ ms}$ with counted warning until K4 merges.
- Populates per-synapse morphology fields (`pre_node`, `post_node`, `path_length`, `dendritic_distance`).
- **Status:** done: `arbor_synapses` stage (cell list over nodes, delays from path length, clipped with a counted warning).

**N4. Connectome SWC skeleton import & simplification.**
- Import reconstructed arbor skeletons from connectome datasets into `ArborStore` format with automatic downsampling/simplification to target compartment budgets.
- **Status:** done: `neurogenesis.data.skeletons` (`simplify_arbor_rdp`, `import_connectome_skeletons`, `attach_connectome_skeletons`).

**N5. Compartmental & dendritic attenuation simulation (post P4).**
- Multi-compartment or passive dendritic filtering approximation in spiking simulation to capture distal vs proximal synaptic efficacy based on `dendritic_distance`.
- **Status:** done: `neurogenesis.dendritic` (`apply_dendritic_attenuation`, `dendritic_attenuate` stage, distal compartment routing for `two_compartment`).

#### Phase J: interoperability, shared metrics, benchmarks and outreach

Goal: make neurogenesis-sim the development front end of the computational-neuroscience ecosystem. Networks grown here
are exported to established simulators and neuromorphic toolchains, and public data (morphologies, gene-expression
atlases, recordings, connectomes) come in as fields, targets and benchmarks. Design rules for every J task:
- **Outside the kernel.** Importers and exporters live in `neurogenesis/interop/` (one module per format) and never
  change development or the integer kernels; determinism and kernel equivalence are untouched.
- **Optional dependencies.** Each format's package is an optional extra (`pip install "neurogenesis-sim[sonata]"`, ...),
  imported lazily, with a clear error naming the extra. CI installs the extras in one job so the tests never skip.
- **Honest mappings.** Every export returns an `ExportReport` listing what is exact, what is approximated (with the
  formula) and what is dropped (e.g. Poisson and burster drivers in formats without them). Formats that can carry
  toolbox-specific attributes (SONATA, NGB1) round-trip losslessly; this is tested.
- **One mapping of the dynamics.** All exporters share one documented translation of the integer LIF into a
  current-based exponential LIF (`neurogenesis/interop/dynamics.py`): `τ = −1 / ln(1 − 2^−shift)` ms (per-tick decay
  matched exactly; `2^shift` overestimates by up to 39 %), `V = E_L + s·v` (default `E_L` −65 mV, `s` = 15/1024 mV,
  so the default threshold is −50 mV), a weight `w` becomes the current jump `W = C_m·s·w·(1 − a)/(4a·τ_syn)`
  (`a = 2^−shift_s`, total charge matched), and a delay of `d` ticks becomes `d + 1` ms (the kernel's extra tick of
  synaptic latency, verified in the tests). Integer rounding, the minimum decrement and the ±2048 clamp are the
  approximation.
- **Licences.** Several target libraries are GPL (navis, NEST), LGPL (libsonata, Norse) or CeCILL (PyNN, Brian 2), and
  the Allen data are non-commercial: optional or test dependencies only, never vendored code or committed data.

**J1. Interop core [contract]. Status: done (`neurogenesis.interop`).** `neurogenesis.interop`: format registry (`register_format`), `export_network(net, fmt,
path, **opts) -> ExportReport`, `import_network(fmt, path) -> SNNNetwork`, `formats()`; `ExportReport` (exact /
approximated / dropped, as JSON); the shared dynamics mapping; CLI `python -m neurogenesis export <network> --format
<fmt> --out <path>` and `python -m neurogenesis import`. Accepts network JSON, NGB1 or an `SNNNetwork`.

**J2. SONATA (input and output). Status: done (`neurogenesis/interop/sonata.py`); BMTK / NEST run pending (J6).** Nodes and edges in HDF5 with node / edge type CSVs and a circuit config (Dai et al.
2020), so BMTK, NEST (via its SONATA reader), NEURON and libsonata tools can read grown circuits. SONATA is the primary export: it is the only target that carries
sparse connectivity, per-synapse delays and extra attributes. Every neuron and synapse field is stored raw (`ngs_*`,
lossless round trip, tested); positions as x, y, z; one population and one attribute group `0` (all libsonata reads);
NEST `iaf_psc_exp` dynamics per node; weights in pA and delays in ms from the shared mapping; edge indices with the
libsonata names (`node_id_to_ranges`). Reader for point-neuron circuits from other tools (inverse mapping). Tested with
libsonata (`node_id_to_ranges` / afferent and efferent edges). Drivers are written as nodes (rates kept); replaying
their spikes as a SONATA spikes input is a follow-up.

**J3. NIR (output, small modules).** The Neuromorphic Intermediate Representation (Pedersen et al. 2024, Nat Commun
15:8122; `pip install nir`): CubaLIF nodes and dense affine / linear weights, for snnTorch, Norse, Lava-DL, SpiNNaker2
and other NIR backends. NIR has no refractory period, no sparse connectivity and no per-synapse delays (its Delay node
is per channel), and its time units are not enforced: suited to small modules only; refractoriness and delays are
reported as approximated, and the time step is written explicitly. Drivers become external inputs or are dropped
(reported).

**J4. PyNN (output).** A self-contained script plus `.npz` arrays: populations of `IF_curr_exp` with the shared
mapping (C_m in nF, weights in nA), positions, `FromListConnector` (pre, post, weight, delay) projections, drivers as
`SpikeSourcePoisson` where possible; runs on PyNN's backends (NEST, NEURON, Brian 2) and SpiNNaker (sPyNNaker).
PyNN can also read SONATA (`pyNN.serialization.import_from_sonata`), which J6 uses as a cross-check.

**J5. NeuroML 2 (output).** `iafRefCell`-type cells, exponential synapses, populations with instance positions,
projections with weights and delays, written with libNeuroML and validated with its validator; for Open Source Brain
and NEURON / NetPyNE import.

**J6. Cross-simulator checks.** For each exporter with a runnable target, simulate reference networks there and in
`lif_int` and compare spike statistics (rates, CV of ISIs, pairwise correlations) with declared tolerances, report only.
Documents how close the exported model is, without claiming equivalence.

**J7. Morphologies in (with N4).** SWC and other skeleton formats through `navis` or `neurom` (no hand-written parser),
into the morphology store of N1, in the frames of I3.

**J8. Gene-expression atlases as fields.** Spatial gene-expression grids (e.g. the Allen Developing Mouse Brain Atlas,
in-situ hybridisation over developmental ages) imported as `GridField`s, registered to a Project's geometry, to drive
`express` / `differentiate` from measured expression (semi-supervised). Dataset manifests in `datasets/` (I1), with
licence and citation.
- **Status:** partial: annotation atlases as geometries (`neurogenesis.data.atlas`: NRRD / MetaImage volumes,
  structure ontologies with descendants, `structure_geometry` → `VoxelMaskGeometry`, `fit_to_box`), used by
  `examples/atlas_relay_mask.py` for the retina → relay example, run on the real P4 annotation of the Allen Developing
  Mouse Brain Atlas (manifest `examples/datasets/allen-devmouse-p4.json`; run 3 of the retina → relay use case, relay
  shaped by p2A because the P4 3D annotation does not delineate the dLGN). Gene-expression grids as fields and
  registration between ages are not done.

**J9. Recordings as fit targets.** Spike data from NWB (pynwb units tables) and neo spike trains, reduced to target
statistics (rates, CV, correlations, PSTHs) for `Fit` Params (E2) and validation (E4).

**J10. Shared connectivity metrics.** One metrics library used by I5, E2 and E4, with the vocabulary of statistical
connectome analysis: distance-dependent connection probability, degree and weight distributions, reciprocity, motif
and directed-simplex counts (Reimann et al. 2019 Nat Commun; Connectome-Manipulator, Pokorny et al. 2025), computed
on grown networks and on imported connectomes alike.

**J11. Public benchmarks.** Two reference problems with data, targets, scripts and a results table: the fly optic-lobe
motion circuit (I6, male CNS and FlyWire connectomes) and a mouse visual-cortex subset (MICrONS). Results are
statistical matches "in this simulation", with the spread between individuals as tolerance.

**J12. Zero-install web demo.** The browser viewer and kernel (Phase F) published with preset projects on the
documentation site, so anyone can grow and simulate a circuit without installing anything.

**J13. Citable releases.** `CITATION.cff`, a Zenodo DOI per tagged release, and a JOSS paper (`paper.md`) once the
repository is public.

#### Phase G: modules, docs and examples

**G1. Module registry.** Versioned modules (semantic version, toolbox version range), a local catalogue file, `neurogenesis install module.json`, compatibility checks.
- **Status:** done: `neurogenesis.modules.registry` (`version` and `toolbox` range on `Module`, catalogue with
  checksums, `install`, `modules`, `get` of the latest compatible version).

**G2. Worked examples**, each runnable on a laptop in minutes, each with a test:
1. a hand-built cortical column, with layers drawn by hand and genes deciding cell types within layers (semi-supervised);
2. a sheet with hand-placed orientation pinwheels, where proliferation and migration produce cell densities that follow the map (semi-supervised);
3. a fully genetic development with proliferation → migration → differentiation → proliferation of differentiated cells → axon growth (unsupervised, evolved on a toy objective);
4. a fully hand-built network (supervised), compiled and simulated.
- **Status:** done: `examples/hand_drawn_column.py` (1), `examples/pinwheel_sheet.py` (2),
  `examples/evolved_development.py` and `examples/layered_sheet.py` (3, with emergent inside-out layering via C14
  `cell_cycle_clock` -> C15 `temporal_competence` -> `differentiate` -> `migrate`, Spearman $r \ge 0.8$ across 5 seeds),
  `examples/hand_built_network.py` (4), each tested in `tests/test_examples.py`.

**G3. Documentation.** A tutorial per workflow (supervised / unsupervised / semi-supervised), the stage reference generated from the registry, and the cost guide.

---

### 5. Order of work and dependencies
```
A1 ─┬─ A3 ─┐
A2 ─┴──────┴─ A4 ─┬─ A5 (backward compatibility)
                  ├─ C1..C13 (parallel)      D1 ─ D2 ─ D4
B1 ─┬─ B2         ├─ D3                      E1 (parallel with C)
    └─ B3 ─┬─ B4  └─ E2 ─ E3 ; E4
           └─ B5
H1 (bridge, Mac agent) ─┬─ H2 (nglif C kernel, Mac agent)
                        ├─ H3 (UI schema export) ───┬─ H5 (Avalonia app, Win+Linux) ── H7 (release)
                        └─ H4 (python-build-standalone) ┴─ (H6 optional Linux extras)

Contracts:
K1 (schema 1.1.0) ─┬─ K2 (Tissue SOA) ─┬─ I1 ─ I2 ─ I3 ─┐
                   │                   ├─ P1 ─ P2 ─ P3 ─┴─ I4 ── I5 (needs E2) ── I6 (needs B3/B4)
                   │                   └─ N1 + N4 ─ N2 ─ N3 (clips >31ms until K4) ── N5 (needs P4)
                   │
                   └─ (after H2) ── K3 (uint32 store) ─┬─ K4 (multi-word delays) ── P4 (channels) ── P5
                                                       └─ N3 full delays

Second wave of stages:
C2 ─ C14 ─ C15 ─ (G2.3 with emergent layering) ;  P3 + P4 ─ C21 ;  C11 ─ C22 ;  K6 ─ C23, C19 ;  K5 ─ C20 ;
N2 ─ C16, C17 ;  P2 + N3 ─ C18 ;  B + D1 ─ C24

Interoperability:
J1 ─┬─ J2 (SONATA) ─┐
    ├─ J3 (NIR) ────┼─ J6 (cross-simulator checks)
    ├─ J4 (PyNN) ───┤
    └─ J5 (NeuroML) ┘
J10 (metrics) ── I5 / E2 / E4 ;  J7 with N4 ;  J8 after B3 + I1 ;  J9 after E2 ;  J11 after I6 + J10 ;  J12 any time ;
J13 at the public release

G1 any time after A1 ; G2 after the stages it uses ; G3 last
```

**Milestones:**
1. **M1:** A1–A5 and D1 merged; the legacy growth runs as a schedule with identical fingerprints.
2. **M2:** B1–B3, C1–C4, C7, C8 (with D2), C13; example G2.3 runs.
3. **M3:** B4, B5, C5, C6, C9–C12, E1, E2; examples G2.1, G2.2 and G2.4.
4. **M4:** H1–H4 merged; native desktop apps (macOS SwiftUI in `desktop/mac/`, Windows + Linux Avalonia in `desktop/avalonia/`) load a Project, dynamically render schedule and parameter editors from the UI schema, run development through the bridge, and simulate live with `nglif`.
5. **M5:** K1–K4, Phase I (connectome data layer & streaming import), Phase P (pharmacology profiles & multi-channel kernels), Phase N (morphology arbors & SWC import); example I6 (optic lobe column benchmark) runs.
6. **M6:** Re-express legacy growth as real C stages (placement, differentiation, identity, guidance) with fingerprints deliberately regenerated and certified.
7. **M7:** J1–J5 and J10 merged: a grown circuit is exported to SONATA, NIR, PyNN and NeuroML 2 with export reports, and
   its connectivity is described with the shared metrics.
8. **M8:** C14, C15, C21–C23 merged; example G2.3 grows its layers from the cell-cycle clock and temporal competence
   (birth order predicts layer across development seeds), with a GABA polarity switch and competitive refinement.


# Page: development/contributing.md

## Contributing

### Code
- New developmental mechanisms are guidance rules (`neurogenesis.development.rules.register`) or new genome keys read by
  `grow`. They must be optional: a genome or plan that does not use them must grow exactly the same network as before.
- Development must stay deterministic: rules do not draw random numbers; any new random draw in `grow` changes every
  network and needs a new major version.
- Every change comes with a test; `pytest` must pass, including the kernel equivalence tests (`cd viz && npm run build:kernel`).

### Modules
A shared module is one `neurogenesis-module/1` JSON file (`Module.save`). It must contain:
- `inputs` and `outputs`: the regions driven by external currents and the regions read out, with a description of the
  input coding (which neuron receives what, at which current) in `description`;
- `validation`: the stimuli or task, the metric, the gate (the per-individual criterion of success), and the pass
  fraction and worst score on development seeds **never used** while the module was designed or evolved (at least 20,
  ideally 100);
- `provenance`: authors, source repository and commit, date, toolbox version.

A module is accepted when its validation can be rerun from these records with the toolbox alone.

### Profiles
A parameter profile (`examples/profiles/*.json`, see `docs/reference/profiles.md`) gives defaults for a species, a region
or an age; the toolbox never depends on one, and any value can be replaced by a `Gene`, a `Field` or a `Fit`. Every
parameter of a profile declares a status (`measured`, `derived`, `assumed` or `free`), what the number is (`quantity`),
and, when it comes from papers, the DOI and what was actually read (`full_text`, `abstract`, `title_only`,
`not_checked`). `python -m neurogenesis profile check --all` must pass; a test runs it on the shipped profiles. A value
applied to a species or region other than the one it was measured in is written as such in `note`.
A numeric stage parameter that users may draw from a gene or a `Fit` gets a range of validity in
`neurogenesis/stages/bounds.py`; `tests/test_stage_bounds.py` then runs the stage on random values and on the corners of
that range, and needs a harness for the new stage.


# Page: development/agents.md

## Rules for contributors and coding agents


### What this repository is
A general toolbox to grow spiking neural networks from genes and hand-made specifications, simulate them cheaply, and
share working circuits as modules. The target is in `README.md` and the plan in `docs/ROADMAP.md`. **Pick one task from
the roadmap (e.g. "C4 migrate"), and stay within its scope.**

### Before you start
0. Get the overview in one read. `python scripts/build_context.py` writes two Markdown files built from the
   documentation (also published on the documentation site and as the `ai-context` artifact of the docs workflow):
   - `neurogenesis-sim-brief.md` (about 11k tokens): project status, README, core concepts, design principles, these
     rules, the CLI and a map of the package. Read it first.
   - `neurogenesis-sim-context.md` (about 70k tokens): the same status section, then every documentation page (roadmap,
     stage reference, formats, protocols, tutorials) in one file.

   Their first section is generated: commit, registered stages, open pull requests, and everything the docs mark as
   partial, not done, PLACEHOLDER or **Not implemented**. Check there that your task is not already done or in an open
   pull request. Both files are generated: never edit or commit them.
1. Read `README.md`, `docs/ROADMAP.md` (sections 1–3 and your task), `docs/EXTENDING.md`, `docs/NEURON_MODELS.md`.
2. Install and run the tests:
   ```
   pip install -e ".[test]"
   cd viz && npm ci && npm run build:kernel && cd ..   # browser kernel, for the equivalence tests
   JAX_PLATFORMS=cpu pytest -q
   ```
   All tests must pass before and after your change, and none may be skipped (CI fails on skipped kernel tests).
3. Work on a branch `task/<id>-<short-name>` and open a pull request that names the task id. Do not push to `main`.

### Invariants: never break these
- **Determinism.**
  - Same inputs and seed give identical outputs.
  - Development randomness comes only from the random streams given to a stage (`rng` argument, one stream per stage,
    see roadmap §2.1).
  - Rules (`neurogenesis.development.rules`) never draw random numbers.
  - Never use global NumPy random state, `random`, or time.
  - `legacy_stream` mode in `run_schedule()` exists exclusively for backward compatibility with legacy plans (fingerprints).
    New schedules must never use `legacy_stream=True`: it breaks the one-stream-per-stage isolation rule.
- **Backward compatibility.**
  - Existing plans and genomes must grow the same networks: `tests/test_development.py` and the legacy-compiler
    fingerprints of task A5.
  - New options are off by default.
  - If a change must alter existing networks, stop and ask the maintainer.
- **Kernel equivalence.** `lif_int` gives identical spikes in Python (`spiking/reference.py`), JAX
  (`spiking/jax_backend.py`), the browser (`viz/src/sim/kernel.ts`), and the portable C kernel `nglif` (`desktop/common/native/nglif/`). Any change to one must be made to all four,
  with the equivalence tests passing.
- **Desktop apps & shared contracts ownership.**
  - Jules agents never edit `desktop/mac/` (owned by the Mac agent) nor `desktop/avalonia/` (owned by the Avalonia agent).
  - `desktop/common/` and `neurogenesis/bridge/` are shared contracts (protocol, NGB1 binary network format, UI schema, visual spec, C kernel `nglif`, golden fixtures, runtime scripts).
  - Change them only in dedicated tasks with a version bump, an updated spec, and all apps' CI passing.
- **Protocol & schema versioning.** Any change to `desktop/common/PROTOCOL.md`, `UI_SCHEMA.md` or Project schema requires backward compatibility, explicit versioning and migration tests.
- **Generality.** No species, organ, task or environment names inside `neurogenesis/`. Use them only in `examples/`,
  in tests as toy content, or in separate module repositories.
- **Contracts.** The Project schema (A1), `Tissue` (A2), `Param` (A3) and the Stage interface (A4) are shared.
  - Change them only within those tasks, or in a dedicated PR that updates every user and the schema version.
  - A change to a contract must also update the docs, migrations and tests.

### Low-compute rules
- Scale linearly: O(N) cells, O(E) synapses.
  - Local interactions go through the shared cell list (task D1).
  - No dense N × N or Ns × Nd arrays except below a documented threshold (state it in the docstring, e.g. `N <= 2048`).
- Use struct-of-arrays, float32 (int32 for the integer kernel), vectorised NumPy or JAX, and no Python loops over
  cells or synapses in hot paths.
- Every stage and model declares its cost (bytes and operations as functions of N, E and its parameters).
  `neurogenesis.cost` uses this before running and refuses what cannot fit. Add a test that the declared cost is within
  a factor 3 of measurement.
- Prefer the CPU path working everywhere: GPUs must be optional, and results must not depend on the device beyond
  floating-point tolerance (integer paths are exact).

### How to add things
| You add... | Where | Must also |
|---|---|---|
| a developmental stage | `neurogenesis/stages/<name>.py`, `@register_stage` | params as `Param`s, cost declaration, neutral parameters = no-op, toy-tissue behaviour test, a docs entry |
| a guidance / placement / weight rule | `neurogenesis/development/rules.py` or a plugin, `@register(phase, name)` | inactive without its genes; listed in the plan's rules; test |
| a neuron model | `neurogenesis/spiking/models/<name>.py`, `register_model` | use `base.drivers` and `base.transmit`; `state_bytes`, `flops`, `substeps`; known-property tests; add to the parametrised tests in `tests/test_models.py` |
| a geometry or field type | `neurogenesis/geometry/`, `neurogenesis/fields/` | JSON round trip through the Project schema; sampling test |
| a GUI panel | `viz/src/ui/` | reads and writes Project files only (no hidden state); `npm run build` passes |
| an example | `examples/<name>.py` | runs on a laptop CPU in under ~5 min; a test in `tests/test_examples.py` |

### Style
- Match the surrounding code: concise docstrings that say what a function does and its units, few comments, and names
  that say what things are.
- Biological mechanisms in docstrings cite their source (first author, year, journal). Do not claim biological fidelity
  beyond what is implemented and tested.
- Units:
  - one tick = 1 ms;
  - local positions in [0, 1]³ inside a region's box;
  - space units in the box (they set conduction delays);
  - currents in toolbox units unless a model states its physical mapping.
- Commit messages: imperative summary line, a body saying what changed and how it was tested.

### Continuous integration
The repository is public, so GitHub-hosted CI is free: every pull request runs the jobs its changes touch (path
filters), and merges to `main` rebuild and publish the documentation site and the AI context files.
- **Do not use `[skip ci]`**, in pull-request commits or in squash-merge titles: every pull request is checked, and
  `main` must be built and its site published after every merge.
- Run the tests locally before pushing anyway (CI is the check, not the debugger), and batch commits: one push per
  finished step.
- All jobs (Python, viewer, `nglif` on Ubuntu / macOS / Windows, `mac-app`) run on every non-draft pull request whose
  files they cover, on `main` and by hand (`workflow_dispatch`); draft pull requests run the Python tests only.
  Runners and their variables are described in `.github/RUNNERS.md`.
- A pull request merges with green checks. Paste the local test summary in the description only when a check cannot
  run (e.g. a macOS-only step).

### Testing checklist for a pull request
- [ ] `JAX_PLATFORMS=cpu pytest -q` passes locally, with nothing skipped (kernel tests need `npm run build:kernel`).
- [ ] CI checks green (or, for a step CI cannot run, the local command and summary line in the description).
- [ ] Commits batched (one push per finished step); no `[skip ci]` in the squash title.
- [ ] New code has tests: determinism; neutral parameters = no-op; the documented behaviour; the cost declaration.
- [ ] Existing networks unchanged (development fingerprints).
- [ ] `cd viz && npm run build` passes if `viz/` changed.
- [ ] Golden fixtures and contract tests pass if `desktop/common/`, `neurogenesis/bridge/`, or `nglif` changed.
- [ ] Design and scope review pass against AGENTS.md and task contracts before merging.
- [ ] Docs updated: README status block and table (checked by `tests/test_readme_status.py`), `docs/EXTENDING.md` or the relevant guide, the roadmap status line of your task,
      `docs/STAGES.md` for a stage, and any PLACEHOLDER your work fills. The status section of the AI context files is
      built from these, so a stale status line or placeholder shows up there as work still to do.

### When unsure
Prefer the smallest change that fulfils the task's acceptance tests. Ask (in the PR) rather than changing a contract,
adding a heavy dependency, or altering existing networks. Pure-Python dependencies beyond numpy / jax / matplotlib need
the maintainer's approval.


# Page: development/extending.md

## Extending neurogenesis-sim and building module projects

### 1. Where new biology goes
| You want to model... | Mechanism | Where |
|---|---|---|
| A new cell population | a `Region` in your plan (fixed placement or grown) | your project |
| Where neurons end up (mosaics, layering, sorting) | a **placement** rule: moves a region's local positions after migration, before signs are drawn | `@register("placement", name)` |
| A new axon-guidance molecule | a **shift** / **target** / **probability** rule: changes where axons aim or the chance of a synapse | `@register(phase, name)` |
| How a receptor transforms a synapse (sign, gain, saturation) | a **weight** rule: modifies the new synapses' weights before rounding and clipping | `@register("weight", name)` |
| A standing current of a cell type (e.g. depolarised at rest) | `genome["tonic"][region]`; add `tonic_current(net, plan, genome)` to the stimulus currents | built in |
| A knockout or an ablation | `knockout(genome, regions=[...], projections=[...])` | built in |

A rule receives a context (`PlacementContext`, `GuidanceContext` or `WeightContext`) and modifies it. Rules must:
1. **read genes and act only when they are present**, so that adding a rule to a plan changes no existing network;
2. **draw no random numbers** (development must stay reproducible for a given seed);
3. **be listed in the plan**, `plan.rules[phase]`. The order in the list is the order of application.

```python
# my_project/rules.py
import numpy as np
from neurogenesis.development import register

@register("weight", "saturating_receptor")
def saturating_receptor(ctx):
    """gd["w_max"]: receptors saturate; weights above w_max are compressed."""
    if "w_max" in ctx.gd:
        m = ctx.gd["w_max"]
        ctx.w = np.sign(ctx.w) * m * np.tanh(np.abs(ctx.w) / m)
```
Then, in the plan:
```python
plan = BodyPlan(..., plugins=["my_project.rules"])
plan.rules["weight"] += ("saturating_receptor",)
```

`plugins` are Python modules imported whenever the plan is built or loaded. A saved module therefore brings its rules
along, provided the plugin package is installed.

Built-in rules, each inactive unless its gene is present:

| Phase | Rule | Genes |
|---|---|---|
| placement | `mosaic` | `genome["mosaic"][region] = {spacing, iters}` |
| shift | `identity_offset` | `guidance[p]["type_c"]` with `genome["identity"][region]` |
| target | `topographic` | `guidance[p]["topographic"] = [1, 1, 0]` |
| target | `lamina` | `guidance[p]["lamina"]` |
| target | `expression_gradient` | `guidance[p]["expression_gradient"] = [{"gene": "EphA", "axis": 0, "gain": 2, "sign": "attract", "normalize": "minmax"}, ...]` (or `{"genes": [...], "mode": "replace" / "modulate"}`); reads `Tissue.expression` columns named by `express(names=...)`; a missing gene or no expression raises `ValueError` |
| probability | `identity_expression` | `guidance[p]["type_s"]` |
| probability | `no_autapse` | always active |
| probability | `source_sign` | `guidance[p]["source"] = "exc" / "inh"` |
| weight | `receptor_sign` | `guidance[p]["receptor"] = "inverting"` |

In a Project, the `grow_axons` stage takes these genes from its `guidance` param, keyed `"src->dst"` (or a list of
`{"src", "dst", ...}`); any value may be a `Gene` read from the genome, e.g.
`"guidance": {"0->1": {"sigma": 0.3, "p0": 0.9, "w": {"type": "Gene", "path": ["axon", "w"], "lo": 10, "hi": 100}}}`.
It does not read `genome["guidance"]`. A listed projection without guidance, or no `projections`/`guidance` at all,
raises ValueError; `"projections": []` is the no-op.

### 2. A module project, step by step
Recommended layout (one repository per module or family of modules):
```
my-module/
  my_module/plan.py        # make_plan(), founding_genome(), handset_genome(); region names are yours
  my_module/assay.py       # stimuli -> external currents; responses -> metrics; the gate of one individual
  my_module/rules.py       # optional plugin rules
  scripts/capacity.py      # hand-set genome: can this architecture do the task at all?
  scripts/evolve.py        # islands (neurogenesis.evolution.evolve) from the founding genome
  scripts/audit.py         # held-out development seeds, knockouts, writes the module JSON
  docs/HYPOTHESES.md       # dated plans, declared before each run, and dated results
  tests/
```
**Order of work:**
1. **Plan and assay first.**
2. **Capacity test** with a hand-set genome. If the architecture cannot do the task, no search will find it.
3. **Evolve** from a founding genome that does not contain the hand-set answer.
4. **Audit** on development seeds never used, including the knockouts that biology says must break the function.
5. **Save** with `Module(..., validation=..., provenance=...)`.

### 3. Using someone else's module
```python
from neurogenesis.modules import Module, compose
retina = Module.load("retina.module.json")             # its plugins are imported, its genes kept
mine = Module.load("my_circuit.module.json")
brain = compose("eye+circuit", {"retina": retina, "circuit": mine},
                links=[("retina.ganglion_on", "circuit.input", dict(B=..., c=..., sigma=0.2, p0=0.5, w=200.0))])
net = brain.grow(seed=0)
```
- **Inputs and outputs** keep their meaning, with the prefix ("retina.photoreceptor").
- **The input coding** (which current means which stimulus) is described in the module's `description` and `validation`.
- **Re-audit the composed system:** a module validated alone may behave differently once connected.


# Page: development/third-party.md

## Third-party software and prior work

Licences were read from the installed packages. This repository is under the MIT licence (`LICENSE`). Update this file
when a dependency, a copied snippet or a key reference is added.

### Code copied or adapted from elsewhere
None identified. The integer kernel, the viewer and the development engine were written for this project; the browser
sandbox has no attribution or licence header from another project.

### Software used at run time
| Package | Licence | Use |
|---|---|---|
| numpy | BSD-3-Clause | arrays, development |
| jax, jaxlib | Apache-2.0 | kernel on CPU / GPU, batching |
| matplotlib | Matplotlib licence (PSF-based) | rasters in the reference simulator |
| three (viewer) | MIT | 3D rendering (WebGL) |
| vite, typescript, esbuild, rollup and their small dependencies (build only) | MIT / Apache-2.0 / ISC / BSD-3-Clause | building the viewer |
| pytest, jsonschema (tests) | MIT | tests |
| h5py (optional extra `sonata`) | BSD-3-Clause | SONATA export / import (`neurogenesis.interop`) |
| libsonata (tests only) | LGPL-3.0 | reference SONATA reader in the tests; never vendored |
| sphinx, sphinx-rtd-theme, myst-parser, sphinx-copybutton (documentation build only) | BSD-2-Clause / MIT | the documentation site |
| GitHub Actions: actions/* (checkout, setup-python, setup-node, upload/download-artifact, upload-pages-artifact, deploy-pages), dorny/paths-filter, dawidd6/action-download-artifact | MIT | CI only, not distributed |

**Desktop app releases.** The embedded runtime (`desktop/common/runtime`, task H4) bundles a CPython build from
python-build-standalone (CPython: PSF License; its bundled libraries such as OpenSSL, SQLite, libffi and zlib keep
their own licences) and the Python packages above. Release bundles must ship those licence files (checked in H7).
The Windows / Linux app (Avalonia, CommunityToolkit.Mvvm, Silk.NET; MIT) lists its NuGet dependencies in
`desktop/avalonia/`.

The viewer loads the Inter and JetBrains Mono fonts from Google Fonts (SIL Open Font License 1.1); opening it sends a
request to Google's servers.

### Methods implemented from the literature
| Mechanism | Source |
|---|---|
| Leaky integrate-and-fire neurons | Lapicque L (1907); Gerstner W, Kistler WM, Naud R, Paninski L (2014). *Neuronal Dynamics* |
| Positional chemoaffinity (axons find targets from graded molecular labels) | Sperry RW (1963). *PNAS*; Eph / ephrin gradients reviewed in McLaughlin T, O'Leary DDM (2005). *Annu Rev Neurosci* |
| Lateral-inhibition differentiation (Notch–Delta) | Collier JR, Monk NAM, Maini PK, Lewis JH (1996). *J Theor Biol* |
| Laminar targeting of axons | Xiao T, Staub W, Robles E, Gosse NJ, Cole GJ, Baier H (2011). *Cell*; Robles E, Filosa A, Baier H (2013). *J Neurosci* |
| Island-model evolution | Whitley D, Rana S, Heckendorn RB (1999). *J Comput Inf Technol* |


# Page: faq.md

## FAQ

**Why an integer neuron model by default?**
Integer arithmetic makes spikes identical across Python, JAX, the browser and C, which makes results reproducible
and lets the apps simulate live with the same kernel. More detailed models (`two_compartment`, `hh`) run on the same
networks when needed.

**Can I model a whole cortical area with Hodgkin–Huxley neurons?**
Ask the estimator first: `python -m neurogenesis estimate --neurons 3e8 --synapses-per-neuron 5000 --model hh`.
On one machine the answer is no (tens of terabytes of synapse memory); the toolbox refuses before trying.

**Is the toolbox specific to one species or brain area?**
No. Region names, cell types, stimuli and tasks belong to your project. Species-specific knowledge (e.g. transmitter
effects) is planned as data profiles outside the core.

**Do evolved circuits explain biology?**
They show what a mechanism can do in the simulation. Statements should be phrased as "in this simulation"; biological
claims need biological evidence.

**How long can delays be?**
Up to 255 ticks (the storage bound of the delay column). The kernels keep a multi-word spike history, (max_delay + 32) // 32
words per neuron (8 words at 255), and give identical spikes in Python, JAX, the browser and `nglif`. Developmental stages
write the distance-derived delay unchanged unless you set a `max_delay` Param, in which case clipped synapses are counted
in a warning.

**How large can networks be?**
The CSR conversion and the browser kernel currently use 16-bit column indices (at most 65,535 neurons); NGB1 and
`nglif` already use 32-bit indices. Lifting the limit everywhere is roadmap task K3.

**My results changed after updating the toolbox.**
Freeze network fingerprints in your project (python dependency) and pin the toolbox commit.

> **PLACEHOLDER.** Questions collected from users once the repository is public.


# Page: citing.md

## Citing neurogenesis-sim

If you use the toolbox in published work, please cite the software and the references of the mechanisms you used
(listed in third party and in the docstrings). The repository's `CITATION.cff` gives the software
citation; GitHub shows it as "Cite this repository".

> **PLACEHOLDER.** Software citation (Zenodo DOI per release, `CITATION.cff`) and the paper describing the toolbox, with BibTeX.

```bibtex
@software{neurogenesis_sim,
  author = {Bellet, Joachim and contributors},
  title  = {neurogenesis-sim: growing spiking neural networks through development},
  url    = {https://github.com/jobellet/neurogenesis-sim},
  year   = {2026},
  note   = {PLACEHOLDER: version and DOI}
}
```

Datasets have their own licences and citations (for example, the FlyEM male CNS connectome is CC-BY 4.0); cite them
when you use them.


# Page: changelog.md

## Changelog

### Unreleased (November 2026)
- Configurable development: Project schema (A1), Tissue (A2), Params and stage scheduler (A3, A4), legacy plans
  compiled into schedules with identical networks (A5).
- Geometry (primitives, CSG, meshes, voxel masks) and fields (grids, analytic fields, layer stacks, operations, maps).
- Stage library C1–C12 (experimental; fix wave in progress).
- Cell list (D1) and sparse guidance (D2); online plasticity (E1).
- Bridge (`python -m neurogenesis.bridge`), NGB1 format, portable C kernel `nglif` with golden fixtures, macOS app.
- This documentation site.

### Earlier
- Integer LIF kernel identical in Python, JAX and the browser; neuron models `two_compartment` and `hh`; calibrated
  cost estimates; body plans, genomes, island evolution, modules; 3D browser viewer.

> **PLACEHOLDER.** Versioned release notes generated from tagged releases.


# Package map (each module's first docstring line)

- `neurogenesis/__init__.py`: neurogenesis-sim: spiking networks grown from genes.
- `neurogenesis/__main__.py`: python -m neurogenesis: command-line generation, simulation and statistics of networks.
- `neurogenesis/analysis.py`: Measures on grown networks.
- `neurogenesis/arbors.py`: Arbor morphologies as struct of arrays (N1): `ArborStore`, SWC read / write, path lengths, downsampling.
- `neurogenesis/bridge/__init__.py`: Bridge between the toolbox and its clients (the desktop apps): a JSON-lines server and the NGB1 network format.
- `neurogenesis/bridge/__main__.py`: Entry point of `python -m neurogenesis.bridge`: runs the local bridge server (`server.main`).
- `neurogenesis/bridge/ngb.py`: NGB1: the binary network format exchanged between the bridge and the desktop apps (desktop/common/PROTOCOL.md).
- `neurogenesis/bridge/server.py`: JSON-lines server that lets desktop apps (and any other client) drive the toolbox: python -m neurogenesis.bridge
- `neurogenesis/cli.py`: Command Line Interface (CLI) for SNN Morphogenetic Network Generation & Simulation.
- `neurogenesis/cost.py`: Compute cost of a simulation before running it: memory, operations and wall time, per neuron model, on this machine.
- `neurogenesis/data/__init__.py`: Connectome data layer (Phase I): dataset manifests and downloads, streaming import, registration, weight mapping.
- `neurogenesis/data/atlas.py`: Annotation atlases as geometries (part of roadmap J8): read a labelled volume and its structure ontology, and turn
- `neurogenesis/data/catalogue.py`: Dataset manifests and the resumable, checksum-verified downloader (I1).
- `neurogenesis/data/importer.py`: Streaming connectome import (I2), coordinate registration (I3) and synapse-count to weight mapping (I4).
- `neurogenesis/data/registration.py`: 
- `neurogenesis/data/skeletons.py`: Connectome skeleton import and simplification (N4).
- `neurogenesis/data/targets.py`: Cell-type statistics of a connectome as fitting targets (I5).
- `neurogenesis/dendritic.py`: Passive dendritic attenuation (N5): synaptic efficacy and delay from the distance along the dendrite.
- `neurogenesis/devcost.py`: Cost of a development schedule before running it (D3): cells, synapses, memory and time per stage.
- `neurogenesis/development/__init__.py`: Development of spiking networks from a body plan and a genome.
- `neurogenesis/development/differentiation.py`: Cell differentiation and molecular identity.
- `neurogenesis/development/grow.py`: Development of a spiking network from a body plan and a genome.
- `neurogenesis/development/legacy_compiler.py`: Legacy compiler (A5): a `BodyPlan` as a Stage schedule.
- `neurogenesis/development/parallel.py`: Parallel development in CPU processes (spawned; JAX restricted to the CPU there). Results equal the serial ones.
- `neurogenesis/development/plan.py`: Body plan: the declarative, genome-independent part of a nervous system.
- `neurogenesis/development/rules.py`: Developmental rules: the extension points of growth.
- `neurogenesis/development/sparse_guidance.py`: Sparse guidance (D2): synapse formation from the guidance rules without an all-pairs matrix.
- `neurogenesis/evolution/__init__.py`: Evolution of genomes (island model) and robustness to developmental randomness.
- `neurogenesis/evolution/islands.py`: Island evolution of genomes coded as [0, 1] gene vectors (neurogenesis.genome).
- `neurogenesis/evolution/robust.py`: Robustness to developmental randomness: a genome must work in every individual it grows, whatever the seed.
- `neurogenesis/evolution/semi_supervised.py`: Semi-supervised optimisation (E3): evolve `Gene` params with `Fit` params refitted in an inner loop or fixed, and
- `neurogenesis/expressions.py`: Restricted safe mathematical and logical expression evaluator.
- `neurogenesis/fields/__init__.py`: Fields (B3-B5): scalar, vector and label fields over space, point patterns and maps, and field operations.
- `neurogenesis/fields/core.py`: Field types (B3) and their JSON registry.
- `neurogenesis/fields/maps.py`: Point patterns and maps (B4).
- `neurogenesis/fields/ops.py`: Field operations (B5), composable in JSON: `Add`, `Subtract`, `Multiply`, `Threshold`, `GaussianSmooth`,
- `neurogenesis/fitting.py`: Supervised fitting of `Fit` params to data targets (E2).
- `neurogenesis/genome/__init__.py`: Genomes: nested dicts of genes; gene addressing, [0, 1] coding, gene groups and JSON.
- `neurogenesis/genome/genes.py`: Genes: (path, low, high) triples addressing values in a nested genome, and their [0, 1] coding for evolution.
- `neurogenesis/genome/groups.py`: Gene groups: the genes of one developmental mechanism for one region or projection.
- `neurogenesis/genome/io.py`: Genomes to and from JSON. Tuple keys (projections) become "src->dst"; arrays become lists and back.
- `neurogenesis/geometry/__init__.py`: Geometries (B1-B2): primitives, set operations, triangle meshes and voxel masks, with a JSON registry.
- `neurogenesis/geometry/mesh.py`: Closed triangle meshes as geometries (B2): `MeshGeometry` with `from_obj` / `from_stl`.
- `neurogenesis/geometry/primitives.py`: Geometry primitives (B1): the `Geometry` interface (`contains`, `sample_points`, `to_dict`), `Box`, `Sphere`,
- `neurogenesis/geometry/voxel.py`: Voxel-mask geometries (B2): `VoxelMaskGeometry`, a boolean occupancy grid with an origin and a voxel pitch.
- `neurogenesis/interop/__init__.py`: Import and export of networks in other simulators' formats (roadmap phase J).
- `neurogenesis/interop/core.py`: Format registry and export reports shared by every importer and exporter (roadmap task J1).
- `neurogenesis/interop/dynamics.py`: One translation of the integer LIF neuron into a current-based exponential LIF, shared by all exporters.
- `neurogenesis/interop/sonata.py`: SONATA network files: writer and reader (roadmap task J2).
- `neurogenesis/modules/__init__.py`: Shareable, composable modules (plan + genome + interface + validation).
- `neurogenesis/modules/module.py`: Shareable modules: a body plan, a genome, an interface and validation records.
- `neurogenesis/modules/registry.py`: Local module registry (G1): versioned modules installed into a catalogue, with toolbox compatibility checks.
- `neurogenesis/neuromodulation.py`: Neuromodulation by volume transmission (P5): a diffuse modulator gating three-factor plasticity.
- `neurogenesis/params.py`: Param sources (A3): how a stage parameter gets its value.
- `neurogenesis/performance.py`: Comparison helpers for fixed performance benchmarks.
- `neurogenesis/pharmacology.py`: Transmitter and receptor pharmacology as data (P1), compiled to signed integer weights (P3).
- `neurogenesis/profiles.py`: Parameter profiles with declared provenance, and profiles that extend other profiles.
- `neurogenesis/project/__init__.py`: Project format (A1): schema and versioning (`schema`), UI schema export for the desktop apps (`ui_schema`).
- `neurogenesis/project/io.py`: Project I/O for reading and writing projects.
- `neurogenesis/project/schema.py`: Project schema validation and migration.
- `neurogenesis/project/ui_schema.py`: UI schema export (H3): `export_ui_schema` describes the stage registry (Params, kinds, bounds, docs, cost
- `neurogenesis/spatial.py`: Cell lists (D1): O(N) fixed-radius neighbour queries without a dense distance matrix.
- `neurogenesis/spiking/__init__.py`: Integer leaky integrate-and-fire networks: data model, reference simulator, JAX kernel (bit-identical), batched runs.
- `neurogenesis/spiking/batch.py`: Batched simulation of many networks with the JAX kernel: each network sees its own sequence of constant stimuli.
- `neurogenesis/spiking/jax_backend.py`: JAX port of the integer spiking kernel (neurogenesis/spiking/reference.py, src/sim/sim.worker.ts), bit for bit.
- `neurogenesis/spiking/models/__init__.py`: Neuron models over one shared transmission layer (synapses, delays, drivers): choose the level of biophysical detail
- `neurogenesis/spiking/models/base.py`: Neuron models: a common interface over one shared transmission layer.
- `neurogenesis/spiking/models/hh.py`: Hodgkin-Huxley point neuron (squid giant axon, Hodgkin & Huxley 1952; modern sign convention, rest near -65 mV).
- `neurogenesis/spiking/models/lif_int.py`: The integer leaky integrate-and-fire kernel (neurogenesis.spiking.jax_backend), bit-identical to the reference.
- `neurogenesis/spiking/models/two_compartment.py`: Two-compartment integrate-and-fire neuron (soma + dendrite): an intermediate level between point neurons and
- `neurogenesis/spiking/morphology.py`: Statistical generation rules and morphogenetic 3D growth cone wiring engine.
- `neurogenesis/spiking/network.py`: Data structures and serialization for 3D Spiking Neural Networks (SNN).
- `neurogenesis/spiking/plasticity.py`: Plasticity rules (E1) on struct-of-arrays synapses, in JAX.
- `neurogenesis/spiking/reference.py`: Fast integer-only SNN simulation engine in Python.
- `neurogenesis/stages/__init__.py`: Stage registry and auto-discovery.
- `neurogenesis/stages/arbors.py`: Arbor growth (N2) and synapses on arbors with path-length conduction delays (N3).
- `neurogenesis/stages/base.py`: Stage interface and scheduler (A4).
- `neurogenesis/stages/bounds.py`: Declared safe ranges of stage parameters.
- `neurogenesis/stages/boutons.py`: Connection multiplicity (C19).
- `neurogenesis/stages/competition.py`: Competitive synapse elimination (C22).
- `neurogenesis/stages/connectivity.py`: Connectivity stages (C8-C9): `grow_axons` (axon guidance and synaptogenesis between region populations through the
- `neurogenesis/stages/critical_period.py`: Critical-period gate (C23): opens and closes synaptic plasticity through the per-synapse `plastic` mask (K6).
- `neurogenesis/stages/differentiation.py`: Expression and differentiation stages (C6-C7, C15).
- `neurogenesis/stages/expansion.py`: Tissue expansion (C24): positions dilate as the cell count grows.
- `neurogenesis/stages/fasciculation.py`: Fasciculation (C16) and Choice Point (C17) developmental stages.
- `neurogenesis/stages/freeze.py`: End of development (C13): validate a tissue and compile it to an `SNNNetwork`.
- `neurogenesis/stages/migration.py`: Migration and sorting stages (C4-C5).
- `neurogenesis/stages/partner.py`: Partner selection by recognition channels (C18).
- `neurogenesis/stages/pharmacology.py`: Transmitters, receptor expression and assignment (P2), and compilation in a developmental context (P3).
- `neurogenesis/stages/population.py`: Population stages (C1-C3, C14).
- `neurogenesis/stages/refinement.py`: Maturation stages (C10-C12): activity-dependent refinement during development.
- `neurogenesis/streaming.py`: Chunked and streaming growth (D4): axon growth for tissues whose synapses do not fit in memory.
- `neurogenesis/tissue.py`: Tissue state (A2): the struct-of-arrays state that stages read and write.
- `neurogenesis/validation.py`: Validation harness (E4): declarative assays stored in a module and rerun by `python -m neurogenesis validate`.
