# 参照：别人是怎么让果蝇在仿真里躲开捕食者的

用户问的是"别人在仿真里做了果蝇躲避青蛙捕食"。查下来，GitHub 上有**三个**相关项目，
而且其中一个和本工程的目标（连接组 → 四旋翼）几乎完全重合。这一页把它们的做法拆开，
说明哪些被我们采用了、哪些没有，以及我们的结果和它们的异同。

## 三个项目

| 项目 | ★ | 语言 | 身体 | 电路 | 频率 | 行为来源 |
|---|---|---|---|---|---|---|
| [`DenisSergeevitch/desktop-fly`](https://github.com/DenisSergeevitch/desktop-fly) | 993 | Swift | macOS 桌面行走者 | **668 神经元 / ~19k 连接**（FlyWire v783） | 1 kHz LIF，神经-身体环 120 Hz | 回路涌现，**零训练** |
| [`SpikeCalls/FlyDrones`](https://github.com/SpikeCalls/FlyDrones) | 95 | Python | **四旋翼**（Tello / Crazyflie / ArduPilot / Betaflight）+ SITL | 全 MaleCNS 166,700；demo 用 850 神经元 MiniFly | 20–50 Hz 控制环 | 回路涌现 + 校准过的线性读出手工调过 |
| [`alextitonis/fly.ai`](https://github.com/alextitonis/fly.ai) | 85 | TypeScript | 无（打格斗游戏） | 全 MaleCNS 166,700 / 25.6M | 50 steps/s（dt=0.002 时 500） | 冻结储备池 + 训练线性读出 |

**FlyDrones 就是用户描述的"果蝇飞无人机"**，而且它把"猛扑才逃"做成了可玩机制：

> 挥苍蝇拍打它 → 苍蝇拍在相机上膨胀 → LPLC2 + LC4 → **巨纤维 DNp01 发放** → 无人机跳起来躲开。
> 太快就打不中。每次躲开，下一次挥拍更快。

它的浏览器 demo 里藏着同样的机制，可以零安装试玩：<https://spikecalls.github.io/FlyDrones/>

## 规模口径：对外主打的数字 ≠ 真正在跑的数字

2026-09-17 重新核对（`gh api` 查 star / README 原文）。这一节是为了防止拿"首页最大的那个数字"去比：

| 项目 | 对外主打的数字 | **真正在跑脉冲仿真的回路** | 突触接触 |
|---|---|---|---|
| desktop-fly | 23,210 个神经元**位置**（FlyWire v783 的 soma 坐标，**渲染**用） | 668 神经元 / 18,968 条连接（FlyWire 雌，1 kHz LIF）＋ 1,045 / 17,224（MaleCNS 运动回路） | 708,689（只算运动那个回路） |
| FlyDrones | 166k 神经元 / ~25M 连接（**它能加载**的图） | **浏览器 demo 用 850 神经元手工搭的 MiniFly**（它自己在 "the honest part" 里标注"不是真实连接组"） | — |
| fly.ai | 166,700 / 25.6M | 真跑全图 | — |
| 本工程 | （原先只在内文写 5,145 / 687,542） | 5,145 神经元 / 687,542 条唯一定向连接，1 kHz，21× 余量 | **5,044,680** |

我们的数字是回原始表里现算的（`connectome-weights-...feather`，151,856,684 行 = 全部检出突触）：

- 全表 151.9M 行 → 过滤后约 25.6M 条连接（FlyDrones 引的就是这个量级）
- 落在我们 5,145 神经元范围内：**687,542 条唯一定向连接对**（每对在表里只出现一次，与 `escape.npz` 的 `nnz` 完全一致）
- 这些连接的权重和 = **5,044,680 个突触接触**，平均 7.3 突触/对，单对最多 2,591

**结论：按"真正参与脉冲计算的规模"，我们不落后**——神经元数是 desktop-fly 最大那个脉冲回路的 3.0 倍，
突触接触数是它的 7.1 倍。但**口径选择上我们吃亏**：同一个回路有两个数，
他们报大的那个（突触接触 708,689），我们报小的那个（连接对 687,542）。
687,542 和 5,044,680 差 7.3 倍，选小的报，读起来就小一个量级。

**待修**：`escape.npz` 只存了归一化权重（float32，仅 100 个不同取值），原始突触计数丢了，
要报这个数字必须回去读 1 GB 原表。导出时应把 `syn_count` 一并存下。

注意这不等于"越大越好"：我们的回路是 `core-hops` 批量扩张出来的（含大量中继），
他们的 668 / 1,045 是**具名回路**（每个神经元都有文献角色）。规模大 ≠ 设计好。

## FlyDrones 的做法（最有参考价值）

### 感官桥接：跳过层板与髓质

这是最关键的一条，也是对我 S1 撞墙的直接解答：

| 输入组 | 细胞类型 | 桥接方式 |
|---|---|---|
| 光感受器 | R1-R6 | 每格平均亮度 |
| 运动 | T4a/b/c/d, T5a/b/c/d | **软件算光流**（Lucas-Kanade）后直接驱动 T4/T5 |
| 逼近 | **LPLC2**（尺寸）、**LC4**（速度） | 光流场的膨胀量（affine 拟合） |
| 平衡 | 平衡棒传入 | 无人机 IMU 偏航角速度 |

他们自己写明：**"我们算光流然后直接驱动 T4/T5，跳过了真实果蝇用来算运动的层板与髓质
（L1-L3、Mi1、Tm3…）"**。理由与我 S1 的实测一致：真实层板神经元是**渐变（非脉冲）**的，
离散 LIF 复现不了，信号在第一级中继就死了。他们的补丁是**换接口**，不是调参数。

### 运动读出：6 个具名下行神经元当操纵杆

| 输出组 | 细胞类型 | 文献依据 | FlyDrones 映射 |
|---|---|---|---|
| 升力/油门 | **DNg02** L/R（≥15 对） | Namiki et al., *Curr Biol* 2022 | throttle ∝ (L+R−静息) |
| 转向 | DNg02 L vs R | 同上 | yaw ∝ (R−L) |
| 规避急转 | **DNp03** L/R | *Curr Biol* 2024 / 2025 | 朝远离威胁一侧偏航，并刹巡航 |
| 逃逸 | **DNp01 / 巨纤维** L/R | Ache et al. 2019 | 率 > 20 Hz → 0.7 s 爬升/俯冲/刹车 |

他们的逃生判据是 **`DNp01 rate > 20 Hz`**（`defaults.yaml` 的 `escape.threshold_hz: 20`）。

### 两个工程选择（他们自己标注为非生物学）

1. **tonic bias**——只给 DNg02 加 9 mV 恒定去极化，充当"我在飞"的内部状态。
   不加的话安静场景下运动神经元完全不发放。
2. **线性读出**——发放率到操纵杆的权重，可用 `flydrones calibrate` 用岭回归拟合；
   拟合只改读出，**从不改连接组**。

他们还明确声明：硬件适配器"写着官方 SDK，但我们没有实飞验证过"；
demo 用的是 850 神经元手工搭的 MiniFly，"不是真实连接组"。

## fly.ai 独立复现了我 S1 的负面结果

这一条让我的 S1 结论有了外部佐证。他们照 Fly64 / Shiu et al. (*Nature* 2024) 的参数配置，
然后发现：

> 用 Fly64 的设置，**视觉完全不起作用**。tonic 0.18、decay e^(−0.2)，
> 使每个神经元的静息膜电位恰好 = 0.18/(1−0.82) ≈ 1.0，**正好是发放阈值**。
> 整个网络以约 4 Hz 自激，运动神经元无论给什么输入都以同样频率发放。
> **光感受器来的信号在第一级中继就死了。** 光感受器释放组胺（抑制性），
> 作用到层板神经元；真实层板神经元用渐变信号而非脉冲，这个简单模型复现不了。
> **我们试过的所有设置里，逼近刺激都到不了逼近检测神经元。**

我 S1 测到的是：`global_drive=0` 时感光细胞烧到 486 Hz，下行神经元脉冲率仍是 **0.00 Hz**；
换成膜电位读出才有 SNR 6.74；`global_drive ≥ 1.0` 就饱和。

**两条独立路径撞上同一堵墙**：把复眼脉冲直接灌进整条中枢链行不通，因为被跳过的第一级中继
本来就是渐变信号。结论相同、解法相同——**直接驱动功能性视觉投射神经元**。

## 采用的 / 没采用的

| 决策 | 来源 | 本工程的处理 |
|---|---|---|
| 跳过层板/髓质，直接驱动 LPLC2/LC4/T4/T5 | FlyDrones | ✅ 采用（`flydrone/reflex.py` 的注入） |
| 具名下行神经元读出，不用"入度最高的 48 个" | FlyDrones | ✅ 采用（DNp01/DNp03/DNg02 为锚点） |
| 逃逸判据 = 巨纤维真发放 | DesktopFly / FlyDrones | ✅ 采用（首次 DNp01 脉冲 + 是否早于触身） |
| 用"行为判据"而不是 loss 验证 | DesktopFly | ✅ 采用（速度扫描 + 通道消融） |
| `core-hops` 核心子图 + `max-neurons` 上限 | FlyDrones `build-brain` | ✅ 采用（`scripts/extract_circuit.py`） |
| 事件驱动（成本 ∝ 发放数×出度） | FlyDrones / 本工程 | ✅ 两边一致（见 `flydrone/neural.py`） |
| tonic bias | FlyDrones | ⚠️ 改成"自发背景电流"，只保底不停自激 |
| 线性读出 + 岭回归校准 | FlyDrones | ❌ 未采用：先做零训练回路，训练留到制导层 |
| 硬件适配器 | FlyDrones | ❌ 未采用：先在本机仿真里把回路验通 |
| 平衡棒 | FlyDrones | ❌ 数据里没有：MaleCNS 的 `type` **无 haltere/campaniform/chordotonal** 注解（实测 0 个），只能如实标注为缺失 |

## 生物学机制：两路在巨纤维相加

Janelia Card 实验室（*Curr Biol* 2017，[Virtual Fly Brain 有视频](http://flybrain.inf.ed.ac.uk/blog/2017/09/06/video-how-the-fly-brain-chooses-between-two-escape-takeoffs/)）：

> 不是单一类型的逼近神经元在驱动巨纤维，而是**两路**：一路编码逼近刺激的**角速度**，
> 一路编码**角尺寸**。模型是这两路信号**在巨纤维里相加**。
> 缓慢逼近的捕食者主要驱动尺寸通道，能兴奋巨纤维但不一定推过阈值，因此偏向"长起飞"；
> 快速逼近同时驱动两路，更早把巨纤维推过阈值，偏向更快、更仓促的"短起飞"。

这正好给出一个**可测的预测**：两路一起给应当比单给任一路更快越过阈值。
本工程直接测了这条预测，结果见 [`findings.md`](findings.md#青蛙实验：慢速被容忍快速才触发)——
**在真实子回路上成立，在符号打乱和线性对照上都消失。**

## 需要如实说明的边界

- DesktopFly 自述：**"这不是完整中枢神经系统，也不是经过生物标定的行走仿真"**；
  飞行、翅振、梳理、睡眠仍是建模动画与状态规则；反应时间不是活体测量值。
- FlyDrones 自述：**"这是一个计算模型，不是一只活着的或被复活的果蝇"**；
  桥接是工程设计的；硬件未实飞验证；demo 用的 MiniFly 不是真实连接组。
- fly.ai 自述：**"这些小实验跑在桌面上，未经同行评审。"**
- 它们的 Demo 视频都是它们自己的标定结果。本工程的数字是我的机器、我的参数下的
  **实测值**，两者不可直接互换。
- 三者都用 LIF，都不是"果蝇脑被完整复现"。**"它做到了"指的是接口设计被验证可行。**
