Skip to content

Tags: programmingWTF/FlyBrain

Tags

v0.3.1

Toggle v0.3.1's commit message
v0.3.1 —— 噪声流可复现性、评测台自包含、游戏性逐 tick 对账

在 v0.3.0 之后继续追查"网页服务端均分偏低",挖出并修掉三个真问题:

1. **噪声走全局 RNG → 同种子也飞得完全不同。**
   同种子、喂完全相同的驱动,两个进程的脑膜电位最大差 **0.674**。
   改成每个脑独占 rng/tgen 后:**0.000000,逐 tick 一致**。
   代价:历史分数不再逐位可复现(留了 `--legacy-noise` 开关做 A/B),
   README 如实记录了实测数字(新 31.05 / 旧 43.65 / 历史 100.80)。
   结论层面完好:cut / shuffled 依旧 **1.00 ± 0.00、0 次拍翅**。

2. **评测台不再需要 51.5 MB 的连接组源数据。**
   `looming.resolve()` 原来无条件读 feather(按设计不入库),
   于是干净的克隆根本跑不了评测台 —— 而仓库里本来就有预烤的 `key_*`。
   已验证预烤与 feather 逐元素完全一致。

3. **`deque(maxlen=5)` vs `recent` 空列表起步** —— 窗口计数的起点不同,
   一旦某 tick DNp01 发放 2 次就开始分叉。

新增 `scripts/verify_server_vs_bench.py`(同种子逐 tick 端到端对账)
与 `scripts/verify_server_gameplay.py`(可复现地量服务端得分)。
三个 bug 都是前者发现的 —— 靠猜是猜不出来的。

至此,网页与评测台**跑的是同一条控制回路**,且由脚本守着。

v0.3.0

Toggle v0.3.0's commit message
v0.3.0 —— 游戏仿真搬到服务端(恒定速度)

网页版从"速度随 HTTP 往返抖动"变成**恒定 50 步/秒、严格 1:1、60fps 流畅**。

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

服务端 `GameWorld`(物理 + 碰撞 + 关卡生成)+ 一个固定 50 Hz 的锁步线程:
每 tick 在同一进程、同一把锁里完成
「按当前世界算驱动 → 走一个脑 tick → 用 DNp01 的发放决定拍不拍 → 走一步物理」。
节拍由**墙钟差值**决定,所以线程被调度得忽快忽慢也不影响游戏速度。

前端只做两件事:按自己的帧率取状态(`GET /api/game/state`,约 400 字节)、
在两次状态之间插值渲染。`app.js` 46 KB → 38.5 KB。

前端试过四种驱动方式,全部失败并都有实测;根因是**浏览器没有可用的可信节拍**:

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

  · scripts/verify_server_physics.py —— 服务端 GameWorld vs 评测台 World
    逐 tick 对账(60 局 × ≤3000 tick,全部死亡,覆盖碰撞路径):逐 tick 等价
  · demo/verify_server_collision.js —— 服务端判定 vs 前端画法,逐像素(12410 个 y,0/0)
  · demo/verify_page.js —— 浏览器验收 + 服务端速率是否恒定,自带进程清理

其中第一个当场抓到真 bug:拍翅冷却原本被我塞进 GameWorld,
而评测台的 `World.step(flap)` 是无条件的(冷却由 harness 管),vy 差了 23.6。

  · 3D 脑图:`viz_spike`/`lit_spike`/`eff` 原来来自前端自己那次 /api/step,
    仿真搬走后面板会**不报错地变空**。现在由状态端点一起发回。
  · `--asset`:默认改成 `spiking_full`,并加启动硬门 ——
    传不含 LC4/LPLC2/DNp01 的资产会直接拒绝启动并说明原因
    (原来是不报错地"分数恒 0",还会先抛一个跟根因无关的 IndexError)。

删掉 `scripts/flappy_page_parity.py`(它继承评测台的 World,
所以"比值 1.0"没有验证力 —— 一个自己和自己比的校验器)、
`demo/measure_rates.js`、`demo/verify_collision_pixels.js`(均已被取代);
文档全面更新(README / demo/README / DEPLOY / check_all.bat)。

v0.2.1

Toggle v0.2.1's commit message
v0.2.1 —— 服务端仿真重构前的检查点

这是"浏览器侧仿真"路线的最后一个可用状态。已验证:

    帧率 27.3 fps   物理 32.1 步/秒   脑 tick 32.1/秒   ← 严格 1:1
    pend 积压 0     脑忙跳过 0        过 19 根管子 / 0 死亡

在页面内直接测量浏览器定时器,得到硬约束:

    setTimeout(4)        → 29.8 次/s(被压到 ~33ms)
    requestAnimationFrame → 28.2 次/s

浏览器把定时器压到约 30 Hz,代码层面绕不过去。而 HTTP 往返中位 60ms
(p90 86ms、最快 4ms,差十几倍)—— 于是"每步物理该配多少时间"时多时少,
表现就是用户报的**时快时慢**。

试过并失败的方案(都已回退,不留死代码):
  · 固定节拍 + 决策队列预取(队列会周期性见底)
  · 大批量预取(一次 20 tick)→ 决策陈旧 → 鸟撞地
  · 预取与消费写在同一 await 链 → 一次往返把消费一起阻塞
  · 依赖 rAF 续命 → 无头/后台页签下 rAF 完全暂停,仿真冻住

把游戏仿真搬到服务端:脑与物理在**同一个进程内**按 1:1 跑固定 50 Hz,
HTTP 只用来取状态,延迟不再进入游戏时钟。服务端已有完整的 Python 物理
(scripts/flappy_bench.py 的 World),且验证过与页面几何一致。

v0.2.0

Toggle v0.2.0's commit message
v0.2.0 —— 自包含 + 可部署的已知可用状态

相对 v0.1.0 的主要变化:

· **仓库自包含**:新增 scripts/bake_coords.py,把 51.5 MB 的 manifest 预烤成
  1.6 MB 的 data/coords.npz(含每个神经元的解剖坐标、556 个关键神经元坐标、
  关键群与逐侧索引)。于是运行期不再需要 manifest.json,也不再需要 13.5 MB 的
  细胞类型表 —— 启动从"解析大 JSON 几十秒"降到 **4.7 s**。

· **运行必需的 3 个资产入库**(共 49.9 MB):
  data/spiking_full.npz / spiking_full.json / coords.npz
  前端 three.js 也进仓库(demo/vendor/three.module.js,1.3 MB)

· **资产缺失时优雅降级**,不再启动即崩(缺 manifest 只让 3D 脑图点云为空)

· **跨平台**:打开浏览器从 Windows 专有的 `cmd /c start` 改成 webbrowser.open()

· **.gitattributes** 锁死行尾,保护 .npz 等二进制资产(本机 core.autocrlf=true,
  猜错会让 Linux clone 下来的文件报废)

· **文档**:DEPLOY.md(systemd/验收清单/排障表/Linux 适用性验证)、
  DEPLOY_PROMPT.md(可复制给服务器 Agent 的提示词)、
  scripts/verify_clone.py(clone 后自检)

实测:启动 4.7 s、内存 483 MB、/api/step 中位 27 ms、
浏览器 36 s 内 score=12 dead=0 无异常。

已知未解决:页面得分仍低于离线评测台(浏览器喂不动 144k 神经元的脑)。

v0.1.0

Toggle v0.1.0's commit message
FlyBrain v0.1.0 —— 首个公开版本

把真实果蝇全脑连接组(FlyWire FAFB v783,144,837 神经元 / 15,023,799 突触)
当成一个**冻结的、零可学参数的神经网络**来驱动小游戏。

**全部 13.9 万神经元的 WebGL 全脑渲染**
  · node fetch_all_skeletons.mjs        → 下载全部骨架(~6 GB,可断点续传,无需 token)
  · node build_brain_asset.mjs --out data/brain  → 两遍流式编译成紧凑二进制
  · node serve.mjs                      → 本地服务 + 自动开浏览器
  · node validate_brain_asset.mjs       → 偏移/对齐/计数逐神经元深校验

**Flappy 演示(本项目主线)**:flyflappy/demo/
  · 脑完全冻结、零可学参数;拍翅只来自 LC4 / LPLC2 → DNp01 的逼近逃逸反射
  · 输入是把"缺口相对鸟的高低"映射到 LC4 腹侧/背侧半群 + LPLC2 角尺寸通道
  · 输出只读 DNp01(巨型纤维两细胞)的发放,过阈值就拍一次翅
  · 神经消融证据:real 108.33 / cut 0.00 / shuffled 0.00(评测台 100 局)

**离线评测台** flyflappy/scripts/flappy_bench.py —— 与页面同一套物理与判定,
用于可复现地量分数、做消融、查死因。

游戏里"看着撞上了却不判失败"这个 bug 查了很久,根因是**像素与坐标混用**。
现在判定由**逐像素**对账定出:

    y - BIRD_R <= p.top + BIRD_R  ||  y + BIRD_R >= p.top + GAP - BIRD_R - 1

依据:fillRect(y,h) 覆盖像素 y…y+h-1(闭区间);上管覆盖像素 [0, top+R]、
下管覆盖 [top+GAP-R-1, …];鸟圆周覆盖 [ceil(y-R), floor(y+R)];两组有交集即视觉重叠。
demo/verify_collision_pixels.js 读 app.js 里的真代码逐像素守着
("只画不判"与"只判不画"都必须为 0)。

  · 页面主循环在慢机器/无头浏览器下抗性不足:渲染慢会导致物理步进被拖住
  · 页面得分仍低于评测台(浏览器喂不动 144k 神经元的脑,实测 14~20 tick/s,需 50 才 1:1)

  · 连接组数据、环境、编译产物均未入库(见 .gitignore,可用上面的脚本重新生成)
  · 暂无 LICENSE(默认保留所有权利)