chore: 工程化改造前的基线快照
在引入 uv 依赖管理、ruff 静态检查与格式化、pyright 类型检查之前, 先把当前可正常运行的状态固定下来,作为后续大规模自动改动的回滚点。 此时状态:97 项测试全绿,HTTP + WebSocket 多牧场服务器可用。
This commit is contained in:
commit
a7725f8812
35 files changed
+11871
No files matched your search
@@ -0,0 +1,49 @@
|
||||
# 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)。
|
||||
@@ -0,0 +1,38 @@
|
||||
# SimpleFarm 项目长期约定
|
||||
|
||||
## 是什么
|
||||
终端农场经营游戏 + 小型多人游戏服务器(v2.0)。游戏跑在服务端进程里,
|
||||
TUI 是纯客户端,同时开放 HTTP + WebSocket 接口给 AI/脚本调用(服务端不内置 AI)。
|
||||
|
||||
## 运行方式
|
||||
- 依赖放隔离 venv:`C:/Users/Administrator/.workbuddy/binaries/python/envs/default/Scripts/python.exe`
|
||||
(服务端需要 fastapi + uvicorn[standard] + websockets;客户端与 TUI 只用标准库)
|
||||
- 服务端:`python main.py serve --daemon`,网页 <http://127.0.0.1:8765/>,文档 /docs
|
||||
- 玩:`python main.py play --farm 名字`;单机:`python main.py solo`
|
||||
- AI/脚本入口:`main.py actions | do <动作> '<JSON>' | batch | state --json | watch | bot`
|
||||
- 测试:`python -m unittest discover -s tests`(97 项;不需要 FastAPI 时只跑 test_engine.py)
|
||||
|
||||
## 架构约定(改动时必须遵守)
|
||||
- **分层**:`farm.engine` 只管规则,不认识网络/终端;`farm.tui` 不认识 socket;
|
||||
`farm/server/*` 不认识终端。新增玩法动作只改 `farm/server/actions.py` 一张表。
|
||||
- **接口同构**:`farm.client.RemoteFarm` 必须与 `FarmEngine` 同名同语义,
|
||||
这样 `tui.py` 不需要区分本地/远程。加动作时两边都要补。
|
||||
- **多牧场**:一个 farm = 一个玩家 = 一份 `saves/<名字>.json` + 一个引擎 + 独立时钟;
|
||||
时钟由**一个** `FarmClock` 线程统一驱动,不要给每个牧场开线程。
|
||||
- **存档类动作**:`save` / `reset` 必须由会话(FarmSession)处理,
|
||||
不能直接落到 `engine.save()`(会写默认路径、覆盖别人的档)。
|
||||
- **实时通道**:WebSocket(`/ws`)是唯一实时通道,推送节流 250ms,消息带自增 `seq`。
|
||||
写操作优先走 WS(带 `id` 回执与 `snapshot`),只有 ws_closed/timeout/network
|
||||
才退回 HTTP 重试;**业务错误必须原样抛出,不能重试**。
|
||||
|
||||
## 环境禁忌(本机特有)
|
||||
- Bash 的 coreutils 残缺(`ls`/`head`/`tail`/`grep` 缺失),**不要用管道**,会整条命令失败;
|
||||
查看/删除文件一律写 Python 脚本。
|
||||
- 环境设了 `HTTP_PROXY` 但没有 `no_proxy`:任何本机 HTTP 访问都要走 `farm/net.py` 的
|
||||
`open_url()`(回环直连),否则会拿到 502 并误判为"服务器没起来"。
|
||||
- Windows 下守护进程的 pid 必须由**服务进程自己**写(见 `farm/server/__main__.py`),
|
||||
父进程的 `Popen().pid` 不可信。
|
||||
|
||||
## 相关文档
|
||||
- 项目入口文档:`README.md`(接口表、WS 协议、边界处理、设计取舍全在里面)
|
||||
- 当天工作记录:`.workbuddy/memory/2026-09-18.md`
|
||||
Reference in new issue
Block a user