为什么:自动化建筑只有水井和喂食器,中期以后每天的操作只剩浇水喂食 的重复劳动。新设施把"替代行动点动作"的思路推到完整流水线: 浇水(井/喷灌)→ 收割机收熟地 → 磨坊把陈粮变饲料 → 喂食器喂牲口 → 售货机趁高价清库存,人不在也能持续产出。 - 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 全绿。
92 lines
6.8 KiB
Markdown
92 lines
6.8 KiB
Markdown
|
||
## 2026-09-23 修网页版「点播种没反应」+ 顺手挖出自动存档失效的真 bug
|
||
|
||
- 用户报网页版点播种没反应。服务端 plant 动作完全正常 → 定位到前端。
|
||
- **根因 1(前端)**:播种按钮走 `window.prompt` 选作物,重置走 `window.confirm`
|
||
——这俩在 sandbox iframe(内置预览面板)里会被**静默屏蔽**:不弹窗、直接返回
|
||
null,于是"点了没反应"。普通浏览器里是好的,所以一直没暴露。
|
||
修复:改成页面内弹层(`#pickmask` + `openCropPick`,未锁定作物生成按钮 +
|
||
取消按钮,点遮罩关闭);重置改两步确认(第一次点变"再点一次确认",3 秒还原)。
|
||
- **根因 2(后端,真 bug)**:顺着验证存档时发现 `default` 牧场**根本没写进库**
|
||
——内存里每次重启都是新档。排查出 `session.dirty` 一个标志位伺二主:
|
||
Publisher 推送循环每 ~250ms 就把 dirty 清掉(无人在线时更是直接清),
|
||
而 `autosave()` 每 60s 才看一眼 dirty——窗口太小,**自动存档几乎永远不触发**
|
||
(约 0.4% 撞率)。进度只在手动存档/优雅停机时落盘,进程被强杀就全丢。
|
||
修复:新增 `session.unsaved` 专管「有变化没落盘」,`_on_change`/`load`/
|
||
`touch`/`advance_day` 置位、只有成功的 `save()` 清位;`autosave()` 改看
|
||
unsaved;dirty 继续只管推送节流。hub.py 的推送循环一字未动。
|
||
- 两只旧测试(`test_autosave_skips_when_clean` / `..._writes_when_dirty` /
|
||
`autosave_all...`)直接操纵 dirty 驱动 autosave,固化了被修掉的耦合——
|
||
全部改成用 unsaved 表达意图。新增回归测试:
|
||
「推送循环清了 dirty 也要照常落盘」「没有新变化不重复写库」。
|
||
- **真机验证**:0 客户端在线、纯 HTTP 种一块地,70 秒内看到库行更新
|
||
(gold 120→112,种子钱都对得上)。
|
||
- 门禁:ruff/pyright 全绿,515 项测试 OK(skip 35)。
|
||
|
||
### 教训
|
||
- 嵌入预览面板里 `window.prompt`/`confirm`/`alert` 都不可信,交互一律用页面内
|
||
弹层实现。
|
||
- 一个布尔标志被两个子系统共用时,其中一个高频清零,另一个低频读取——
|
||
低频的那个等于瞎了。存盘依据必须由"成功的写入"来清。
|
||
|
||
## 2026-09-23 用户名登录(多用户)
|
||
|
||
- 用户要求「添加多用户的 需要使用用户名登录」;AskUserQuestion 确认:
|
||
**仅用户名**(不设密码)+ **一账号一个牧场**(用户名即牧场名)。
|
||
- 新增 `farm/server/auth.py`:`LoginManager` 签发/校验 HMAC-SHA256 签名的
|
||
HttpOnly Cookie(`sf_auth`,30 天,SameSite=Lax)。Cookie 值 =
|
||
`base64("用户名|过期时间") + "." + hmac`,验签 `hmac.compare_digest`。
|
||
密钥 32 字节存 `<save_dir>/secret.key`(0600)——**重启不掉线**,无需建表。
|
||
- `app.py`:新增 `POST /api/login`(guard 照常检查全局 token)、
|
||
`POST /api/logout`、`GET /api/me`(刻意不 guard,首屏探测用);
|
||
`_Services` 加 `auth` 字段与 `user_of()`;**三个认 ?farm= 的地方全部改为
|
||
「有 Cookie 强制用自己的牧场」**:`session_of`(?farm=/X-Farm-Id 被忽略)、
|
||
SSE `/api/stream`、`POST /api/farms`。无 Cookie 的脚本/AI/CLI 保持旧语义
|
||
(互信小圈子,`?farm=` 不受影响),bot/TUI 零改动。
|
||
- `webui.py`:移除「牧场名」输入框和「进入牧场」按钮,改为登录遮罩
|
||
(`#loginmask` 复用 `.mask/.pick` 样式)+ 用户徽章 `#who` + 退出按钮;
|
||
JS 先 `whoami()` 再决定 `connect()` 还是弹登录;令牌输入框保留
|
||
(change 时重连)。farmName() = 登录用户。
|
||
- 测试 +13:`LoginCookieTestCase` 9 项纯逻辑(伪造/过期/跨密钥/垃圾串/非法名/
|
||
密钥复用/session_of 优先级——starlette Request 可直接用 scope dict 构造,
|
||
不需要 httpx)+ AuthTestCase 4 项 TestClient 档(照例 skip)。
|
||
门禁 528 项 OK(skip 39),ruff/pyright 全绿。
|
||
- 真机验收 16/16:登录/Cookie/HttpOnly/?farm= 被忽略/动作落自己牧场/
|
||
篡改退回未登录/退出/非法用户名 400/首页元素。跨重启:secret.key 复用,
|
||
Cookie 仍认 carol、显式 save 后作物也在。
|
||
- 教训:幂等补丁脚本里 `new == ""` 时 `if new in text` 恒真,会误报 ALREADY
|
||
跳过删除类替换(这次是删全局 Enter 监听那行,靠核对输出抓回来)。
|
||
删除类编辑别走补丁脚本,直接用 Edit 工具。
|
||
|
||
## 2026-09-23(晚)「种不了东西」:中文用户名 + X-Farm-Id 头
|
||
|
||
- 用户报种不了东西。API 直发 plant 全正常、页面代码静态看着也正常,
|
||
最后用 headless Chrome(本机 Chrome + `--cdp 9333` 连接模式)真机复现:
|
||
登录"阳"后点播种→选小麦,toast 不变、地块不变,act() 抛
|
||
`TypeError: Failed to execute 'fetch': String contains non ISO-8859-1 code point`。
|
||
- **根因**:`headers()` 把用户名塞进 `X-Farm-Id` 头,HTTP 头只允许 Latin-1,
|
||
中文用户名让 fetch 在发送前就抛异常。登录时 me=null(头是 ASCII 的
|
||
default)所以能登录成功,**登录后所有走 api() 的写操作全部静默失败**
|
||
(Promise 拒绝无人接住);页面靠 SSE 照常渲染,看起来一切正常。
|
||
- 修复(webui.py,幂等补丁 6 处全中):headers() 非 ASCII 值不塞头
|
||
(登录态走 Cookie,头只是匿名脚本备用通道);api() 捕获 fetch 异常并
|
||
toast,杜绝静默失败;localStorage 裸调用加 try/catch(sandbox iframe
|
||
无 allow-same-origin 时访问即抛,启动段有三处裸调用会炸掉整个脚本)。
|
||
- 真机回归:登录阳→点播种→选小麦→toast「1 号地已种下小麦」+ 地块状态
|
||
实时更新。门禁 528 项 OK(skip 39)、ruff/pyright 全绿。提交 2676927。
|
||
- 顺带发现:上一轮遗留的调试实例用 `--tick 60` 跑了好几个小时(day 冲到
|
||
92),已用默认 40 分钟/天重启。
|
||
- **推送排障(重要环境坑)**:push 卡死 5 分钟 = **system 级
|
||
`credential.helper=helper-selector` 在等人点选择框**(进程
|
||
git-credential-helper-selector.exe)。修法:global 里放**空 helper 重置
|
||
列表**再依次挂 wincred + GCM(`!"C:/Program Files/..."` **必须带 `!` 和
|
||
引号**,否则空格截断)。GCM 路径还要 `NO_PROXY=gitea.4i.hk`——系统代理
|
||
对它 502。修完推送秒过。Chrome/Edge 直接 `--executable-path` 起不来
|
||
(exit 0 无端口),要自己 headless 起进程再 `--cdp <port>` 连。
|
||
|
||
### 教训
|
||
- HTTP 头只允许 Latin-1:任何把用户可输入内容(用户名、中文名)塞进请求头
|
||
的代码,中文输入即炸 fetch。要么不塞(走 Cookie/body),要么白名单校验。
|
||
- api() 层必须接住网络异常并可见化——静默的 Promise 拒绝让这个 bug 从
|
||
"登录功能上线"一直活到用户报障。
|