Files
SimpleFarm/.workbuddy/memory/MEMORY.md
T
snowy-yang cb4c1e7800 feat(engine): 四座自动化设施——喷灌 / 收割机 / 售货机 / 磨坊
为什么:自动化建筑只有水井和喂食器,中期以后每天的操作只剩浇水喂食
的重复劳动。新设施把"替代行动点动作"的思路推到完整流水线:
浇水(井/喷灌)→ 收割机收熟地 → 磨坊把陈粮变饲料 → 喂食器喂牲口
→ 售货机趁高价清库存,人不在也能持续产出。

- config.BUILDINGS 注册 4 座:喷灌系统 2800 金(全量浇水)、
  自动收割机 900 金 ×3(每天各收 2 块熟地,刻意不给经验——机器收的
  不算劳动,给手动玩法留价值)、自动售货机 1200 金(市价 > 基准价才
  出手、每天上限 5 份、越贵越先卖)、饲料磨坊 500 金(1 份最便宜的
  作物磨 3 份饲料)。
- engine._run_buildings 扩成六段流水线,顺序即依赖:磨坊排在喂食器前,
  早上磨的饲料当天就能喂上;收割机遇满仓停摆、作物留地(同喂食器
  缺料停摆哲学——自动系统绝不硬来,更绝不自动花钱)。
- webui / build 动作 / catalog 全部由 config.BUILDINGS 驱动,零改动。
- 测试 +6(喷灌全量浇水、收割入库且无经验、满仓停摆留作物、磨坊挑
  最便宜、磨坊→喂食器同日链路、售货机阈值与配额)。修复断言时确认
  两个既有行为:引擎自带 START_FEED=5;advance_day 末尾会把动物 fed
  复位("喂到了"的证据是 pending 累计而非 fed 本身)。
- 门禁:534 项 OK(skip 39)、ruff/pyright 全绿。
2026-09-25 21:05:27 +08:00

5.6 KiB
Raw Blame History

SimpleFarm 项目长期约定

是什么与运行

终端农场游戏 + 多人服务器(HTTP + SSE 给 AI/脚本;TUI/网页纯客户端)。 uv 管理:所有命令带 uv run ;依赖只改 pyproject(uv add/remove),uv.lock 要提交; requirements.txt 是导出产物只读。门禁(改动后必跑): uv run ruff format . && uv run ruff check . && uv run pyright && uv run python -m unittest discover -s tests (现 528 项;TestClient/真机档 39 项因无 httpx 自动 skip;ruff check . 会扫 tests, 测试文件要自己保持 lint 干净)。性能复核 uv run python tools/bench.py --repeat 5。

玩法与存档

  • 一天 40 分钟(DEFAULT_TICK_SECONDS=2400);没有手动快进一天的入口,别再加。
  • 天气 5 种决定水分蒸发,state.forecast 明天提前可见;收获进 inventory 谷仓、 sell 才换钱;建筑每天在 advance_day 里先于生长执行。
  • 存档 SQLite saves/farm.db 一牧场一行:标量列 + 整份 payload JSON(加字段改 _raw_state(),不改表结构);SAVE_VERSION=2 / MIN=1 向后兼容;老 JSON 启动时自动 迁移(原文件 *.json.migrated 留底)。StorageError = sqlite3.Error,别写 except OSError。 WAL 是持久属性,建库设一次即可。测试钩子:advance_day(weather="sunny") 钉死天气。

架构约定

  • 分层:engine 不认识网络;server/actions.py 是动作唯一登记表;RemoteFarm 与 FarmEngine 同名同语义(加动作两边都补)。StateView.apply():带 plots=整体替换、 不带=合并字段。farm/keys.py 是按键名唯一来源(全小写),tui.py 不许手写字符串字面量。
  • session 的 dirty(推送节流,250ms 清)与 unsaved(自动存档依据,只有 save() 清) 必须分开——合并会让 autosave 瞎掉。网页禁用 window.prompt/confirm(嵌入预览里被 静默屏蔽),交互一律页面内弹层。
  • 用户名登录(2026-09-23):仅用户名、一账号一牧场(用户名即 farm_id)。 auth.py 签发 HMAC HttpOnly Cookie sf_auth(30 天),密钥 <save_dir>/secret.key (重启不掉线)。app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms) 都是有 Cookie 强制本人牧场、无 Cookie 保持 ?farm= 旧语义(脚本/AI 兼容)。 动登录逻辑别破坏这条边界;/api/me 刻意不 guard。
  • SSE /api/stream 是唯一实时通道(单向);写操作一律 POST /api/actions;停机必须 hub.close_all()。uvicorn+zttp HTTP/2:uvicorn 0.53 不内置 h2,必须 http="zttp"; 信号接管=子类覆盖 Server.handle_exit(不调 super());as_asgi() 是 pyright 类型桥。
  • app.py 结构:create_app 只装配,路由全在模块级 _register_*;轻量路径只走 brief_source()(BRIEF_KEYS 白名单要同步);>60 行的函数应视为信号。
  • 守护进程:路径只有文件名时用 ensure_parent_dir()(别手写 makedirs(dirname)); pid 由服务进程自己写;端口自检用 port_busy()(判断「能连上」而非「能 bind」)。

本机环境(coreutils 残缺禁管道 / LF / 句柄等详见用户级记忆 ~/.workbuddy/MEMORY.md)

  • RemoteFarm.snapshot() 是本地缓存不发请求;realtime=False 的脚本必须自己 refresh(), 否则轮询读到旧数据像卡死。
  • 本机 HTTP 一律走 farm/net.py 的 open_url()(环境有 HTTP_PROXY 没 no_proxy)。
  • 幂等补丁脚本注意:new == "" 时 if new in text 恒真会误报 ALREADY 漏改; 删除类编辑直接用 Edit 工具。
  • 批量改同一文件用幂等补丁脚本(read_bytes/write_bytes + APPLIED/ALREADY/MISS); 同一文件一条消息里多次 Edit 会被静默覆盖。

远端与推送

  • https://gitea.4i.hk/hhhge/SimpleFarm(public,main)。推送: NO_PROXY=gitea.4i.hk no_proxy=gitea.4i.hk GCM_INTERACTIVE=always git push origin main (NO_PROXY 缺了被系统代理拦成 502;GCM_INTERACTIVE 缺了 GCM 拒交互报 "Failed to authenticate")。还不行加 GCM_TRACE=1。沙箱会丢 .git/refs/remotes 写入使 status 显示 [gone]——假象,验证推送看 git ls-remote。
  • credential.helper 链已修(2026-09-23 晚):system 级 helper-selector 会开 选择框等人点导致 push 永久挂死,已用 global 空 helper 重置列表顶掉, 现为:"" → wincred → !"C:/Program Files/Git/mingw64/bin/git-credential-manager.exe" (必须带 ! 和引号,否则空格截断)。push 反复挂死先 taskkill 清 git.exe/GCM 僵尸(占 GCM 单例锁)。凭据库有 GCM 刷新令牌 git:https://refresh_token.gitea.4i.hk。
  • 真浏览器验收(UI bug API 测不出):headless Chrome 自己起进程 (--headless=new --remote-debugging-port=9333 --user-data-dir=临时目录, 后台任务保活),agent-browser 用 --cdp 9333 连(每条命令都要带); 系统浏览器 --executable-path 直起会 exit 0 连不上。详见项目技能 simplefarm-verify-and-ship。

webui 前端铁律(2026-09-23)

  • HTTP 头只允许 Latin-1:用户可输入内容(中文用户名)塞进请求头会让 fetch 发送前抛 TypeError。headers() 里非 ASCII 一律不塞(登录态走 Cookie)。
  • api() 必须接住 fetch 异常并 toast——静默 Promise 拒绝曾让登录后所有写 操作失效却毫无痕迹。
  • localStorage 裸调用全走 lsGet/lsSet 兜底(sandbox iframe 无 allow-same-origin 时访问即抛,会炸掉整个启动脚本)。

文档

README.md 是入口文档(接口表、SSE 协议、设计取舍);当天记录在 .workbuddy/memory/YYYY-MM-DD.md。