Files
SimpleFarm/.workbuddy/memory/2026-09-18.md
T
snowy-yang a7725f8812 chore: 工程化改造前的基线快照
在引入 uv 依赖管理、ruff 静态检查与格式化、pyright 类型检查之前,
先把当前可正常运行的状态固定下来,作为后续大规模自动改动的回滚点。

此时状态:97 项测试全绿,HTTP + WebSocket 多牧场服务器可用。
2026-09-18 21:12:40 +08:00

50 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-09-18 · SimpleFarm 从单机 TUI 改造为小型多人游戏服务器
## 需求
用户先要求"CLI/TUI 农场游戏 + HTTP/gRPC 接口让 AI 参与";中途改方向:
**放弃 gRPC,改用 FastAPI/aiohttp**,要求:守护进程可后台运行、SSE 或 WS 二选一、
TUI 只在用户玩时作为客户端、用户也能用 HTTP 调用、支持多用户(小型游戏服务器)。
## 最终架构(v2.0)
- 服务端 `farm/server/`:`app.py`(FastAPI REST+WS) / `actions.py`(动作表) /
`session.py`(多牧场会话 + 单一 FarmClock 统一时钟) / `hub.py`(WS 连接池 + 250ms 节流推送) /
`daemon.py`(守护进程) / `webui.py`(内嵌网页客户端)。
- 客户端 `farm/client.py` 的 `RemoteFarm`:与 `FarmEngine` **接口同构**,
所以 `tui.py` 不用改渲染逻辑就能从"本地引擎"切到"远程服务器"。
写操作优先走 WS(带 id 回执),通道类错误自动退回 HTTP;读数靠 WS 推送。
- `farm/bot.py` 规则机器人 + `ai_player.py` 外挂机器人:只调接口,不 import 游戏规则。
- 统一入口 `main.py`:serve/stop/status/logs/play/solo/do/batch/actions/state/log/watch/bot/farms/help。
## 关键决策
- **选 WebSocket 而非 SSE**:这个游戏动作上行和状态下行是同一件业务的两半,
SSE 只能下行,等于要维护两套鉴权/重连。
- **删掉 gRPC**(`farm/api.py` 旧 stdlib HTTP 层、`farm/grpc_server.py`、`proto/`、`tools/gen_proto.py`):
接口主要喂 LLM/脚本,它们只会捏 HTTP+JSON。
- 多牧场模型:一个牧场 = 一个玩家 = 一份存档 + 一个引擎;空闲 30 分钟先落盘再移出内存。
- 动作表 `server/actions.py` 是所有入口(HTTP / WS / CLI / 机器人)的唯一执行通道,参数校验只写一次。
## 踩过的坑(重要,可复用)
1. **本地请求被系统代理截走**:机器上设了 `HTTP_PROXY` 但没有 `no_proxy`,
`urllib` 把发给 127.0.0.1 的请求也交给代理,本机服务器报 `502 upstream connect failed`。
解决:`farm/net.py` 的 `open_url()`,回环地址走 `ProxyHandler({})` 直连。
2. **pid 归属**:起初父进程把 `Popen().pid` 写进 pid 文件,但 Windows 上 venv 的
`python.exe` 可能是转发器,`os.getpid()`(40564)≠ 父进程看到的 pid(20244),`stop` 会杀错进程。
解决:`--pid-file` 传给子进程,子进程在 lifespan 启动(端口已绑定)后写自己的 pid,退出时删除;
`stop` 优先走 HTTP `/api/admin/shutdown` 优雅停机(能落盘),拿不到 pid 也能停。
3. **精简状态会清空视图**:动作回执里的 `state` 是精简版(无 `plots`/`animals`),
客户端若整体替换就会把农田格子清空。解决:`StateView.apply()` 按"含 plots 即整体替换,
否则合并字段",并且 HTTP 动作带 `full=1` / WS 回执带 `snapshot` 让客户端立刻刷新。
4. **`save` 动作必须由会话处理**:`engine.save()` 会写默认路径,多牧场下会互相覆盖;
在 `app.execute_action()` 里把 save/reset 拦截为会话级操作。
5. 本机 Bash 的 coreutils 坏了(`ls`/`head`/`tail`/`grep` 都不存在),
所有查看/清理动作改用 Python 脚本完成;管道会直接让命令失败,**不要用管道**。
## 验证
- 97 项测试全绿(引擎 45 + 服务器 52),含真 uvicorn + 真客户端 + 真 WebSocket 端到端。
- 实测:daemon 启动 → status 正确 pid → actions/do/batch → 两牧场隔离与排行榜 →
WS 收到 welcome/state/result/day → 机器人 120→197 金币 → stop 优雅停机落盘。
- TUI 远程渲染验证:`tools/preview_tui.py --url http://127.0.0.1:8793 --farm alice`
(112 列 / 76 列都正常,头部显示 `牧场名 @ 服务器`)。
- 依赖装在隔离 venv:`C:/Users/Administrator/.workbuddy/binaries/python/envs/default/Scripts/python.exe`
(fastapi 0.141.1 / uvicorn 0.53 / websockets 17.1 / httpx 0.28)。