Skip to content
programmingWTFPublic

About

用真实果蝇全脑连接组(FlyWire FAFB v783, 144,837 神经元)当作冻结的零参数神经网络驱动的 Flappy —— LC4/LPLC2 → DNp01 逃逸反射

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

FlyBrain · Flappy —— 用真实果蝇脑玩 Flappy

把 FlyWire FAFB v783 果蝇全脑连接组(144,837 神经元 / 15,023,799 突触) 当成一个完全冻结、零可学参数的神经网络,去驱动一只小鸟飞过管子。

不训练。 用的是连接组里真实存在的一条逃逸反射通路:

视觉逼近  →  LC4(104 个)/ LPLC2(210 个)  →  DNp01(巨型纤维,2 个细胞)  →  拍翅

一句话结论

组(同参数、同 40 局) 均分 拍翅次数
real:真实接线 100.8(旧噪声流,见下) 256
cut:切断 LC4/LPLC2 → DNp01 0.00 0
shuffled:同度分布随机重连 0.00 0

cut 与 shuffled 都是 0 次拍翅 —— 脑子不再指挥翅膀。 这就是"分数必须穿过真实连接组"的硬证据。

⚠️ 上表那些分数是在"旧噪声流"下测的(见下方"噪声流与可复现性")。 现在的代码用每个脑独占的噪声流,所以重跑会得到统计上同类、但不逐位相同 的数字。这不是回归,而是修掉了一个可复现性缺陷 —— 具体原因写在那一节里。

网页版现在是恒定 50 步/秒、严格 1:1、60fps 流畅: 游戏仿真跑在服务端(GameWorld + 固定 50 Hz 锁步线程), 前端只取状态 + 插值渲染。见 页面 vs 评测台。


怎么跑

零第三方前端依赖:只用 Python 标准库 http.server + 仓库自带的 three.js。 后端需要 numpy / pandas / torch(CPU 版即可),需要 Python ≥ 3.10。

# 依赖(CPU 版 torch 就够)
pip install numpy pandas torch --index-url https://download.pytorch.org/whl/cpu

# 起服务(默认资产就是 spiking_full,无需额外参数)
OMP_NUM_THREADS=1 python demo/server.py --no-browser --port 8620
# 浏览器打开 http://127.0.0.1:8620/

--asset 默认已是 spiking_full,正常不用传。 这里曾经是个静默失败陷阱:默认到不含 LC4/LPLC2 的 spiking_circuit 子图时, 脑完全不发放、分数恒为 0,而且不报任何错 —— 很容易被当成"反射不行"。 现在传了不含关键群的资产会在启动时被直接拒绝,并说清是哪一种问题 (群缺失 / 预烤坐标索引越界)。

Windows 下也可以直接双击 demo/run_demo.bat(关闭用 demo/stop_demo.bat)。 部署到 Linux 服务器请看 DEPLOY.md(systemd、验收清单、排障表)。

离线评测台(量分数、做消融)

PY=python                                    # 或你的 venv 解释器

# 双向逼近反射,60 局
$PY scripts/flappy_bench.py --asset spiking_full --modes bidi --games 60 --max-ticks 5000

# 神经消融三连(这是全项目的重点实验)
$PY scripts/flappy_bench.py --asset spiking_full --modes bidi --games 40 \
    --graph real      --out output/abl_real.json
$PY scripts/flappy_bench.py --asset spiking_full --modes bidi --games 40 \
    --graph cut       --out output/abl_cut.json
$PY scripts/flappy_bench.py --asset spiking_full --modes bidi --games 40 \
    --graph shuffled  --out output/abl_shuffled.json

控制回路

环节 做法
仿真 LIF 脉冲网络。dt=0.02 τ=0.1 gain=3.0 tonic=0.14 threshold=1.0,带 1.2 Hz / 0.22 幅度噪声
输入 把"缺口相对鸟的高低"映射到 LC4 腹侧/背侧半群(双向逼近反射),再加 LPLC2 角尺寸通道
输出 只读 DNp01 的膜电位;过阈值就拍一次翅
可学参数 0 个

一个绕不开的量化事实:DNp01 每个 tick 需要 need = (1−leak)·thr/gain = 0.06042 的持续驱动,而它是漏积分的 (leak = 0.81873)—— 所以单次刺激不够,必须持续逼近才会发放。 整个控制回路的节奏都由这一条决定。

双向逼近反射

方向由缺口在视野里的高低决定:

  • 缺口在上方 → 驱动 腹侧 LC4 半群 → 爬升
  • 缺口在下方 → 驱动 背侧 LC4 半群 → 下潜

再加接近速度门控(地面只在下落时逼近、天花板只在上升时逼近)。 神经计算全部在后端;前端只取状态、只渲染(不再算几何、不再喂脑)。

谁在推进时钟

环节 位置 节拍
脑 tick 服务端 Session._game_tick 固定 50 Hz(20ms/tick,走墙钟)
物理步 服务端 GameWorld.step 与脑 tick 严格 1:1(同一进程、同一把锁)
渲染 浏览器 自己的帧率(实测 60fps),只取状态 + 插值

控制回路是 scripts/flappy_bench.py 的同构版本: 按当前世界算驱动 → 走一个脑 tick → 用 DNp01 的发放决定拍不拍 → 走一步物理。 决策与物理步严格配对,中间没有网络、没有定时器节拍。

前端曾经要自己跑这个回路,试过四种驱动方式全部失败(见 页面 vs 评测台)—— 根因是浏览器没有可用的可信节拍(setTimeout(4) 被压到 29.8 次/s、 rAF 后台会被完全暂停),而 HTTP 往返抖动十几倍。


算法教材

python scripts/make_doc_figures.py --out output/doc_figs python scripts/make_doc_page.py(写到桌面)

产出是一份自包含 HTML 教材(约 390 KB,双击即可离线阅读),20 节,分七部 + 附录:

  1. 全局 —— 五分钟看懂、20ms 闭环总图
  2. 计算基础 —— LIF 神经元、驱动门槛 need = 0.06042 的推导
  3. 感觉与指令 —— LC4/LPLC2 通路、几何→驱动、够不够触发的判据
  4. 反射 —— 双向逼近反射、回路闭合
  5. 游戏世界 —— 物理与像素语义、关卡生成
  6. 证据 —— 消融实验、逐 tick 对账、可复现性与噪声
  7. 工程 —— 为什么必须服务端、六个真实 bug、负结果
  8. 附录 —— 常见误解、术语表、一页带走

写法上每节分三层(直觉 / 形式 / 本项目具体数字), 配手算算例、17 道可展开的自测题、常见误解与速查表。

两条防脱节的措施:

  • 配图全部取自真实数据 —— 栅格图从跑着的仿真里逐 tick 取发放, 膜电位图按 LIF 公式逐步算出(读者可拿纸笔核对每一点), 分布图是真实生成器的 4000 次输出。
  • 正文里的常数直接从 demo/server.py 读(make_doc_page.py 里 import), 所以改了代码重新生成,文档里的数字自动跟着变,不会出现"文档写 0.06、 代码是 0.05"这种情况。

关卡生成:一个把生成器逼死的约束

"相邻缺口的向上跳变不能超过执行器带宽"这条约束是对的(鸟一个间隔最多净爬约 125 px, 更大的跳变物理上飞不进去)。但原来的实现方式让它退化了:

# 旧:从下方均匀抽样
uniform(lo, min(hi, prev_c + max_climb - GAP/2))

一旦缺口中心下沉,这个上界就缩到低于下界,于是退化成 uniform(70, 70) —— 恒等于 70。 实测 200 根管子:top 全是同一个值、标准差 0.0、上升次数 0/199。 那不是"不够随机",是生成器死了;而且只用到量程的一半。

现在改成以上一根缺口中心为中心的有界随机游走,两条界都设:

next_hi = min(c_hi, prev_c + max_climb)    # 向上:受执行器带宽约束(110 px)
next_lo = max(c_lo, prev_c - max_drop)     # 向下:给一个与缺口等高的界(230 px)
c = uniform(next_lo, next_hi) + N(0, 16²)  # 再叠独立抖动
c = clip(c, next_lo, next_hi, c_lo, c_hi)  # 抖动之后必须重新夹

两个容易踩的点,都写在代码注释里了:

  1. 抖动必须夹在约束之内。 我第一版把抖动加在夹取之后, 实测最大上升 211 px,超过了 110 的上限 —— 抖动把"物理可达"这条硬约束冲掉了。
  2. 只设向上界不够。 那样缺口中心只能活在 [c_lo, c_hi] 死区里, 一旦被推到边界,下一根必须落在死区内 → 被迫向下掉 216 px。 这不是设计,是边界效应。

效果:缺口 top 从"恒定一个值"变成覆盖 70..250 全量程、标准差约 50 px。 代价是单局均分从约 36 降到约 31(20 局口径,在方差内)。 参数扫描见 scripts/tune_levels.py。


分数是怎么一步步上去的

离线评测台 scripts/flappy_bench.py,前几档 200 局/条件:

阶段 均分 中位 最高 做了什么
被动(不控) 0.00 0 0 基线,鸟自由落体
单侧反射 1.54 1 8 只接一路(页面早期做法)
腹侧 LPLC2 地面反射 2.44 2 13 抬���下界
双向逼近反射 7.20 5~6 26 缺口高就爬、低就潜
+ 按执行器带宽生成关卡 29.52 21 72 关卡变得物理上可达
+ dors_scale=0.35 / vent_gain=2.0 / 首管余量 100.8 115 115 两个外部增益旋钮 + 第一根管子留够反应时间

最后一档是 40 局、max_ticks=8000(所以"最高 115"是被 tick 上限截断的, 不是真实上限)。前面几档是 200 局、max_ticks=5000。

关卡可达性是执行器带宽卡死的:管距 300 px / 240 px·s⁻¹ = 1.25 s, 而鸟的可持续爬升率只有约 100 px/s(scripts/flappy_climb_rate.py 实测), 所以一个间隔最多爬约 125 px。原来缺口跳变有 330 px 量程 —— 超出的关卡无论如何都飞不进去。把向上跳变限制在 40 px 以内(向下不设限)之后, 分数从 7.2 跳到 29.5。管宽、缺口高、管速、重力一个都没改。


优化过的那些"量"(含负结果)

每一行都能在 output/*.json 里找到原始产物。

杠杆 实测
dors_scale(背侧增益) 单调:0 → 40.40,0.5 → 40.15,0.35 → 42.1,1.0 → 30.98(天花板撞死 24/80),1.5 → 21.55(38/80)
vent_gain(腹侧增益) 1.0 → 39.96,1.5 → 62.05,2.5 → 62.41,4.0 → 62.41(饱和,被 tick 上限卡住)
game_slack(物理步 / 脑 tick) 0.694 → 2.07(这正是页面的实际比值)· 0.83 → 7.55 · 1.0 → 111.25 · 1.15 → 122.23(最优)· 1.24 → 76.18 · 1.4 → 19.92
ceil_boost 1.0 / 1.4 / 2 / 3 ❌ 32.29 / 25.24 / 22.20 / 21.61 —— 全部更差
提高驱动增益 ❌ 爬升率 gain 1→2→4 只从 87 到 101 px/s 就饱和
上冲期间保留腹侧驱动 ❌ 更差(9.45 → 5.12),因为它破坏了下潜支
放宽重力 ❌ 试过,反而更差
门控类变体(ground_eq / ground_eq2 / ground_sp) ❌ 全部 0 分
1-ply 预测 / lookahead ❌ 自由落体基线 3.02、1-ply rollout 0.00(两个分支在 vy=-316 时行为相同)
通道消融 cut / shuffled ❌ 0.00(见上)

game_slack = 0.694 那一行特别值得注意:它正是页面当年实测的物理/脑速率比 (50 / (60×1.2) ≈ 0.694),得分只有 2.07。这把"页面为什么远低于评测台" 从猜测变成了可复现的实验。 现在这个现象不会再出现了:仿真搬到服务端之后,页面与评测台的速率比就是 1.0 (两边跑同一份物理、同一个 50 Hz),并且由 scripts/verify_server_physics.py 守着。


碰撞判定:为什么它需要逐像素对账

游戏里"看着撞上了却不判失败"这个 bug 查了很久,根因是像素与坐标混用。 最终判定由逐像素对账定出(现在这条判据在 server.py 的 GameWorld.step 里, 因为物理已经搬到服务端):

if (self.y - self.BIRD_R <= p["top"] + self.BIRD_R
        or self.y + self.BIRD_R >= p["top"] + self.GAP - self.BIRD_R - 1):
    return self.die("撞上管子")

推导依据(闭区间像素):

  • fillRect(y, h) 覆盖像素 y … y+h-1
  • 上管覆盖像素 [0, top+R]、下管覆盖像素 [top+GAP-R-1, …]
  • 鸟圆周覆盖像素 [⌈y-R⌉, ⌊y+R⌋]
  • 两组像素行有交集 ⇔ 视觉重叠

要求"只画不判"与"只判不画"同时为 0。历史上错过两次,而且方向相反 (<= / >= 和 < / > 各差一段 R,实测都是"只画不判" 35~37 个 y)。

离线校验器(都不影响运行时性能)

脚本 守什么 结果
demo/verify_server_collision.js 权威:服务端判定的碰撞集合 == 前端画面重叠集合,逐像素,读 server.py + app.js 里的真代码 12410 个 y 位置,偏差 0 / 0
scripts/verify_server_physics.py 物理等价:服务端 GameWorld vs 评测台 World 逐 tick 对账 60 局 × ≤3000 tick,逐 tick 等价
demo/verify_page.js 浏览器验收:页面无异常、渲染帧率、服务端仿真速率是否恒定 60.0 fps / 50.05 步每秒 ✅
scripts/verify_server_vs_bench.py 控制回路端到端:服务端 vs 评测台,同种子逐 tick(y/vy/发放/拍翅)对账 3 局 × ≥1071 tick,逐 tick 一致、总分相同
scripts/verify_server_gameplay.py 服务端真实 Session._game_tick 能拿多少分(可复现的固定种子) 见脚本输出
scripts/tune_levels.py 关卡生成的"随机性 vs 可玩性"参数扫描(可复跑) 见脚本输出
scripts/make_doc_figures.py 从跑着的仿真取数,生成教材配图(非示意图) 闭环图 + 220 tick 栅格 + 膜电位算例 + 分布对照
demo/verify_pipe_visual.js 管子形状不变量(不越出管宽、像素行连续、贴住画面上底) ✅
demo/verify_bird_volume.js 画出来的鸟外接半径 == 判定半径 17.00 px == 17 px ✅
demo/check_syntax.mjs 批量语法检查(不查未定义标识符,见下) ✅

⚠️ check_syntax.mjs 抓不到"调用了不存在的函数"。 我因为这个吃过大亏: 删掉 brainBusy() 之后页面报 ReferenceError,而语法检查一路绿灯。 凡是删函数的改动,都必须真的把页面跑起来(demo/verify_page.js 会抓异常)。

同样地,把物理搬到服务端时我两次把测试范围写错, 于是好代码被报成坏代码(一次扫了画面外的 y、一次扫了真实游戏生成不出的 top)。 校验器本身也要能被质疑。


页面 vs 评测台:差距已消除(仿真搬到服务端)

曾经页面上只有 27 分而评测台 100+,差距定位为"浏览器喂不动脑"。 现在这个问题已经从根上解决:游戏仿真整体搬到服务端(server.py 的 GameWorld

  • 一个固定 50 Hz 的锁步线程),前端只取状态、只渲染。

为什么"在前端想办法"是不可能的

前端一共试过四种驱动方式,每一种都实测过,每一种都失败:

# 驱动方式 实测结果
① 物理跟墙钟走 + 每帧消化 1 步 帧率 <50fps 就积压几十步,鸟控不住
② 物理跟帧率走 帧率 11.5fps 时只有 12% 速度,飞不起来
③ 物理跟脑响应走 速度 = HTTP 往返速度 → 时快时慢
④ 固定节拍 + 决策队列预取 队列周期性见底,仍然抖

根因是结构性的:游戏时钟必须挂在某个节奏上,而浏览器里只有两个可选项, 页面内实测都不行:

setTimeout(4)            → 29.8 次/s   (被浏览器压到 ~33ms)
requestAnimationFrame    → 28.2 次/s   (后台还会被完全暂停)

再��上 HTTP 往返 中位 60ms / p90 86ms / 最快 4ms(差十几倍)—— 只要决策要靠一次跨进程往返拿到,时钟就一定是抖的。

搬到服务端之后

脑与物理在同一个进程、同一个锁里按固定 50 Hz 跑严格 1:1(先按当前世界算驱动 → 走一个脑 tick → 用 DNp01 的发放决定拍不拍 → 走一步物理), 没有任何网络或定时器节拍进入游戏时钟。前端只做两件事:按自己的帧率取状态、 在两次状态之间插值后画出来。

指标 前端自跑(④) 服务端锁步(现在)
仿真速率 14~32 步/秒(抖) 50.0 步/秒(恒定,偏差 +0.1%)
渲染帧率 与游戏速度耦合 60 fps,与游戏速度无关
决策与物理 靠队列近似配对 同一进程内严格 1:1
网络抖动影响 直接改变游戏速度 只影响画面新鲜度

物理等价性

服务端的 GameWorld 与评测台的 World 必须逐 tick 等价,否则"分数"就没意义了。 scripts/verify_server_physics.py 用同一串拍翅命令对账两边的 位置/速度/分数/死亡时刻/死因/管道布局:

对账 60 局 × ≤3000 tick(其中 60 局死亡,覆盖了碰撞路径)
✅ 服务端 GameWorld 与评测台 World 逐 tick 等价

这个脚本当场抓到了一个真 bug:我最初把拍翅冷却塞在 GameWorld 里, 而评测台的 World.step(flap) 是无条件执行拍翅的(冷却由它的 harness 管), 于是 vy 差了 23.6(评测台 -316.4、服务端 -292.8)。冷却门已移到调用方。


噪声流与可复现性(一个真实缺陷及其修复)

SpikingBrain.step() 的泊松噪声原来读全局 RNG(np.random.binomial / torch.randint / torch.rand)。后果是"同一个种子"根本保证不了可复现: 噪声序列取决于此前的代码消耗了多少随机数。

这不是理论担忧,是实测出来的。我在给"网页服务端 vs 离线评测台"做逐 tick 对账时 (scripts/verify_server_vs_bench.py)发现:两边用同一个种子、喂完全相同的驱动, 脑的行为却不一样 ——

DNp01 发放数:服务端 2 / 评测台 1
膜电位最大差:0.674

原因是两个进程初始化时消耗的随机数个数不同(服务端要建 3D 点云抽样、可点亮清单等), 于是从第一 tick 起噪声就错开了。表现就是"同一套代码、同一个种子,却飞得完全不同"。

修复:每个脑持有自己的 rng / tgen,reset() / set_seed() 时按种子重置。 修完之后同样的对账:膜电位最大差 0.000000,逐 tick 完全一致。

这个修复的代价(实测数字,不粉饰)

噪声序列变了,所以 README 里历史测出的具体分数不再逐位可复现。 我留了一个 --legacy-noise 开关(把噪声切回全局 RNG)专门用来做这个对照:

配置(bidi、40 局、5000 tick、first_gap_extra=160) 均分 中位 最高
新噪声流(可复现,当前默认) 31.05 ± 25.47 22 72
旧噪声流(--legacy-noise) 43.65 ± 27.56 46 72
README 历史引用的数字 100.80 ± 37.57 115 115

读数要注意两点:

  1. 噪声流本身值 ~12 分(标准误 4.2,所以这个差是显著的)。 也就是说这个修复确实把分数拉低了一截,不能假装没发生。
  2. 但历史那个 100.8 在两条路径上都复现不了(43.65 也远不到 100.8)。 它是在更早的代码 + first_gap_extra=0 + 8000 tick、 且 35/40 局存活到上限的条件下产生的。我无法在现在的代码上复原它 —— 记录在这里,免得以后有人拿它对不上数、又不知道为什么。

结论层面没有变化

消融三连在当前代码下重测(40 局 / 5000 tick):

组 均分 拍翅/局 存活 tick
real(真实接线) 31.05 80.6 2184
cut(切断 LC4/LPLC2 → DNp01) 1.00 ± 0.00 0.0 40
shuffled(同度随机重连) 1.00 ± 0.00 0.0 40

cut / shuffled 一次都不拍翅(0.0 次/局、方差 0),40 局全部死在第 1 根管子。 "分数必须穿过真实连接组"这个结论完好无损。

那个 1.00(而不是 0.00)来自一种结构性白送,不是脑在做事: 第 1 根管子的起点是 x = 120 + 0.75·240 − 62 = 118,而鸟在 x = 120 —— 鸟一出生就在第 1 根管子的缺口里;再加上开局 0.8s 的加速期不判碰撞, 而第 1 根管子移出鸟身只需要约 40 tick(= 0.8s), 于是加速期一结束它就已经"过"了第 1 根。所以裸分是 1。


诚实的边界

  • dors_scale / vent_gain 是外部手写旋钮,不是连接组里的东西。 它们调节"感觉信号有多强",作用在脑的输入端;连接组内部结构没有改过。
  • 模型时钟 dt = 20 ms,而真实巨型纤维通路是 1.4–25 ms, 所以延迟类数字只能定性。
  • LC4 的灵敏度 s50 是外部假设(文献里没找到 LC4 的实测发放率), 它可调、会改变触发早晚,但不改变"有阈值、且由那 102 条边携带"这个结论。
  • 预测/前瞻类尝试全部失败,已如实记为负结果(见上表)。
  • scripts/flappy_page_parity.py 已删除。它当初继承了评测台的 World, 所以"比值 1.0"没有验证力(一个自己和自己比的校验器)。 这个坑值得记住,所以写在这里:校验器本身也要能被质疑 —— 它在仓库里存在了很久,一直报"通过",却什么都没验。 它的职责现在由 scripts/verify_server_physics.py 承担(那一个是真对账)。
  • 服务端仿真是单例:Session 是全局共享的一个,所以多个浏览器窗口会互相干扰 (看到同一个游戏)。演示够用,不适合对外公开(无鉴权)。
  • 服务端占一个核:50 Hz 锁步线程会持续占用一个 CPU 核(约 500 MB 内存)。 闲置时也照跑,因为"随时打开页面都是活的"体验更好。

目录

路径 内容
src/fpv/ LIF 脉冲内核、逼近通路解析、任务几何
demo/server.py 网页后端:脑会话 + 服务端权威的游戏世界与 50 Hz 锁步线程
demo/app.js 前端:只取状态 + 插值渲染,不跑物理也不喂脑
demo/verify_page.js 浏览器验收:页面起得来、无异常、渲染流畅、服务端速率恒定
demo/verify_server_collision.js 逐像素对账:服务端判定的碰撞 == 前端画出的管子
demo/verify_pipe_visual.js / verify_bird_volume.js 画法不变量(管子形状、鸟的外接半径)
scripts/flappy_bench.py 离线评测台:同一套物理与判定,可复现地量分数 / 消融 / 查死因
scripts/verify_server_physics.py 物理等价性:服务端 GameWorld vs 评测台 World 逐 tick 对账
scripts/verify_server_vs_bench.py 控制回路等价性:服务端 vs 评测台同种子逐 tick 对账(含脑)
scripts/verify_server_gameplay.py 量服务端真实得分(可复现固定种子)
scripts/make_doc_figures.py / make_doc_page.py 生成一份算法教材(自包含 HTML,20 节,配图与正文常数都取自真实仿真与源码)
scripts/bake_coords.py 把 51.5 MB 的 manifest 预烤成 1.6 MB 坐标(让仓库自包含)
scripts/loom_*.py 逼近反射那一套实验(阈值、视野覆盖、机制)
data/ 连接组资产。只入库运行必需的 3 个(见下),源数据用脚本重新生成
DEPLOY.md 部署指南(依赖、systemd、验收清单、排障表)
DEPLOY_PROMPT.md 可直接复制给服务器 Agent 的部署提示词
ESCAPE.md 主要报告:这条通路的完整量化分析(含全部负结果)
SCORE_PROMPT.md / HANDOFF.md / FINDINGS.md / PLAN.md 接手提示词与过程记录

部署到服务器

看 DEPLOY.md。一句话版 —— 仓库是自包含的:data/ 里三个文件 (spiking_full.npz 50 MB / spiking_full.json 226 KB / coords.npz 1.6 MB)已入库, three.js 也在 demo/vendor/ 里,所以:

pip install numpy pandas torch --index-url https://download.pytorch.org/whl/cpu
python demo/server.py --no-browser --port 8620

--asset 的默认值是 spiking_full,正常不用管它。 这个默认值原来是个静默失败陷阱:默认到 spiking_circuit(不含 LC4/LPLC2) 的后果是分数恒为 0、脑一次都不拍翅,而且不报任何错。 现在已经反过来 —— 传了不含关键群的资产会在启动时被直接拒绝, 并且说清是哪一种问题(群缺失 / 预烤坐标索引越界)。

本机实测:启动 4.7 s、内存约 500 MB、服务端仿真线程持续占一个 CPU 核 (固定 50 Hz 跑脑 + 物理);回退到 < 5% 占用。

状态端点是 GET /api/game/state(前端每帧取,约 400 字节); /api/step 仍然保留,用于「一次性推进 N 个 tick」的离线式调用, 但页面已经不再用它(页面走 /api/game/state)。


数据来源

项 值
数据源 FlyWire FAFB v783(codex.flywire.ai)
神经元 144,837
突触 15,023,799(兴奋 11,043,448 / 抑制 3,152,462 / 未知符号 827,889)
平均出度 103.7(最大 9,773)
游离神经元 6,963(无突触)

已知问题 / 下一步

  • 把仿真搬到服务端 —— 页面 < 评测台的唯一正解
  • 页面主循环抗性不足:渲染慢时物理步进会被拖住(应改成物理与脑完全解耦)
  • scripts/flappy_page_parity.py 需要重写 → 已删除,职责移交 scripts/verify_server_physics.py
  • 前瞻 / 预测控制器尚未成功

许可

暂无 LICENSE(默认保留所有权利)。

About

用真实果蝇全脑连接组(FlyWire FAFB v783, 144,837 神经元)当作冻结的零参数神经网络驱动的 Flappy —— LC4/LPLC2 → DNp01 逃逸反射

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages