把 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 节,分七部 + 附录:
- 全局 —— 五分钟看懂、20ms 闭环总图
- 计算基础 —— LIF 神经元、驱动门槛
need = 0.06042的推导 - 感觉与指令 —— LC4/LPLC2 通路、几何→驱动、够不够触发的判据
- 反射 —— 双向逼近反射、回路闭合
- 游戏世界 —— 物理与像素语义、关卡生成
- 证据 —— 消融实验、逐 tick 对账、可复现性与噪声
- 工程 —— 为什么必须服务端、六个真实 bug、负结果
- 附录 —— 常见误解、术语表、一页带走
写法上每节分三层(直觉 / 形式 / 本项目具体数字), 配手算算例、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) # 抖动之后必须重新夹两个容易踩的点,都写在代码注释里了:
- 抖动必须夹在约束之内。 我第一版把抖动加在夹取之后, 实测最大上升 211 px,超过了 110 的上限 —— 抖动把"物理可达"这条硬约束冲掉了。
- 只设向上界不够。 那样缺口中心只能活在
[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)。 校验器本身也要能被质疑。
曾经页面上只有 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 |
读数要注意两点:
- 噪声流本身值 ~12 分(标准误 4.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.pyWorld, 所以"比值 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(默认保留所有权利)。