snowy-yang
|
2676927c1a
|
fix(webui): 中文用户名登录后所有写操作静默失败
为什么:headers() 把用户名塞进 X-Farm-Id 请求头,而 HTTP 头只允许
Latin-1 字符——中文用户名(如"阳")会让 fetch 在发送前直接抛
TypeError(String contains non ISO-8859-1 code point)。登录时
me 还是 null(头为 ASCII 的 default)所以能登录成功,登录之后
me 变成中文,所有走 api() 的写操作(播种/浇水/收获/卖出)全部
静默失败;页面靠 SSE 推送照常渲染,看起来一切正常,用户表现为
"点播种没反应"。
- headers():非 ASCII 的牧场名/令牌不再塞头。登录态本来就由
Cookie 鉴权,X-Farm-Id 只是匿名脚本模式的备用通道;ASCII 名
(default、bot 等)行为不变。
- api():捕获 fetch 网络异常并 toast 提示。此前 Promise 拒绝
无人接住,这类故障完全不可见,正是本 bug 难排查的原因。
- localStorage 读写加 try/catch 兜底:sandbox iframe 若不带
allow-same-origin,裸访问即抛 SecurityError,会炸掉整个启动
脚本(启动段就有三处裸调用)。
验证:headless Chrome 真机复现 → 修复 → UI 全流程回归(登录
"阳" → 点播种 → 选小麦 → toast 与地块状态正确更新);ruff /
pyright / 528 项测试全绿。
|
2026-09-23 22:28:40 +08:00 |
|
snowy-yang
|
698a5e497a
|
feat: 用户名登录——一账号一个牧场,登录后改 URL 也进不了别人的档
多用户之前是「知道牧场名就能玩」:?farm=boss 直接进别人家里。
现在网页端用用户名登录(用户选择:不设密码、一账号一个牧场,
用户名即身份——互信小圈子模型,防的是随手改 URL,不是蓄意攻击)。
* 新增 farm/server/auth.py:HMAC-SHA256 签名的 HttpOnly Cookie
(sf_auth,30 天,恒时比较防篡改)。密钥存 <save_dir>/secret.key,
重启不掉线,也不用建表存 token。
* app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms)
统一规则:有 Cookie 强制本人的牧场(?farm=/X-Farm-Id 被忽略),
无 Cookie 保持旧语义——bot/TUI/CLI 脚本零改动照常工作。
* 新路由:POST /api/login(全局 token 照常把它挡在门外)、
POST /api/logout、GET /api/me(刻意不 guard:首屏只问"登录没")。
* 网页:移除牧场名输入框,改为登录遮罩 + 用户徽章 + 退出按钮;
首屏先 whoami() 再决定连接还是弹登录。
测试 +13:Cookie 伪造/过期/跨密钥/垃圾串/非法用户名/密钥跨重启复用/
session_of 优先级(starlette Request 用 scope dict 直接构造,不需要
httpx)9 项纯逻辑档 + 4 项 TestClient 档。528 项 OK(skip 39)。
真机验收 16/16:登录/HttpOnly/?farm= 被忽略/动作落自己牧场/篡改
退回未登录/退出/非法名 400;跨重启 Cookie 仍有效且进度保留。
|
2026-09-23 21:20:25 +08:00 |
|
snowy-yang
|
305ef0dcbe
|
fix: 网页播种/重置在嵌入预览里没反应——弃用 window.prompt/confirm
播种按钮原来弹 window.prompt 选作物,重置弹 confirm——这俩在
sandbox iframe(内置预览面板)里会被静默屏蔽:不弹窗、直接返回
null,表现为"点了没反应"。普通浏览器没事,所以一直没暴露。
改成页面内交互:播种弹 #pickmask 弹层,列出未锁定作物的按钮 +
取消,点遮罩关闭;重置改两步确认,第一次点变"再点一次确认",
3 秒不点自动还原。
|
2026-09-23 20:54:11 +08:00 |
|
snowy-yang
|
cf24cb43f1
|
feat: 加天气 / 谷仓 / 建筑三套系统,收获改为入库 + 择时卖出
动手前先修掉了两处「死机制」,新增的三个系统各自激活一个:
* 天气:小麦 2 天成熟、初始水分 2、每天只掉 1 点,等于永远不需要浇水。
现在水分消耗由天气决定(晴 1 / 多云 0 / 干旱 2,雨天反补 3),而
`state.forecast` 把明天的天气提前告诉你——「今晚浇不浇」成了能算的决策。
5 种天气、权重合计 100、同种不连出;暴风雨会毁未熟作物,温室免疫。
* 谷仓:市场波动原本纯装饰(收获当场按市价变现,没有择时空间)。现在收获与
收取只进 `state.inventory`,靠新增的 `sell` 换金币,价格波动才真正参与决策。
仓满时 `_store()` 抛 `granary_full` 且不改状态,作物原样留在地里。
* 建筑:新增 `build`,买的是「以后不用天天手动做」——水井自动浇干地、
喂食器自动喂食(只吃库存饲料,绝不自动花钱)、温室、谷仓扩容。
每天在 `advance_day` 里先于生长执行,同类越造越贵(×1.6)。
存档升到 `SAVE_VERSION=2`,配 `MIN_SAVE_VERSION=1` 向后兼容,旧档缺的新字段
一律走默认值,不作废玩家手里的档。`advance_day(weather=...)` 是留给测试的
确定性入口。
顺带修两个真 bug:
* `daemon.py` 三处 `makedirs(os.path.dirname(path))`:`--pid-file pid.txt`
这种只给文件名的用法会让 dirname 变成空串,启动阶段直接崩。抽出
`ensure_parent_dir()` 兜底。
* `RemoteFarm.snapshot()` 是本地缓存,非实时客户端轮询会一直读到旧数据,
看起来像服务器卡死。补 docstring 指明要自己调 `refresh()`。
质量门禁:ruff format/check 全绿、pyright 0 错误、503 项测试 OK(skip 35)、
bench 无回归(brief 19.5µs / snapshot 48.5µs / POST full=1 400µs)。
另起真实服务器(--tick 3 + 预置存档)跑了 38 项端到端验收,全过。
|
2026-09-19 17:02:33 +08:00 |
|
snowy-yang
|
f8e093c98f
|
feat: 实时通道改 SSE、服务器换 uvicorn+zttp、节奏改 40 分钟一天,并重建测试与优化热路径
三件事凑成一次不兼容变更:WebSocket 通道、Hypercorn 服务器、「手动推进一天」都消失了。
实时通道:WebSocket -> SSE
- hub 改为 SSE 订阅池,GET /api/stream 单向推送 welcome/state/day/notice/bye,
250ms 节流 + 15s 心跳注释;写操作一律走 HTTP POST /api/actions,不再有 WS 回执。
- 停机三处一起修才干净:close_all() 发 bye + timeout_graceful_shutdown 兜底 +
客户端 stop() 直接 shutdown 底层 socket(光 close() 会等读线程的锁,最长卡到下次心跳)。
- 网页版改 EventSource + fetch;tools/ws_demo.py 换成 tools/sse_demo.py。
服务器:Hypercorn -> uvicorn + zttp 的 HTTP/2
- 同一端口同时说 HTTP/1.1 与 HTTP/2。uvicorn 0.53 不再内置 h2,必须装 zttp 并显式
http="zttp",否则 Config.load() 直接 raise。
- 信号接管改成子类覆盖 Server.handle_exit(0.53 没有 install_signal_handlers 可覆盖),
不调 super()、不写 _captured_signals,退出时不重放信号;第二次信号强制退出。
- 入口启动前用 daemon.port_busy() 自检端口:Windows 上 SO_REUSEADDR 会让第二个实例
「绑上」已监听端口并抢走新连接,所以判断「能不能连上」而不是「能不能 bind」。
- 修掉 clear_pid_file 的 Windows 真 bug:文件还开着就 os.remove 会失败且被
except OSError 吞掉,导致每次正常退出都留 stale pid 文件。
节奏:一天 15 秒 -> 40 分钟,并整体移除「休息」
- DEFAULT_TICK_SECONDS=2400,MAX_TICK_SECONDS 放宽到 86400(旧上限会把 40 分钟夹回去)。
- engine.sleep / SLEEP_COOLDOWN / /api/sleep / TUI 的 [N]休息 / 网页版按钮全部删掉,
时间只能自然流动;机器人改为 wait_for_next_day(timeout),CLI 加 bot --wait。
- 时长显示统一走 models.format_duration(),不再出现 2399.6s 这种写法。
键盘:修复方向键/空格/Tab/ESC 全部静默失效
- keys.py 的具名键统一小写并成为唯一来源,tui.py 一律引用 keys.XXX 而不手写字符串
(历史 bug 就是 Windows 侧产大写 UP、TUI 里写小写 up,字母键正常所以很难发现)。
- 顺带修 POSIX 路径一次 os.read 只取首字节、其余按键静默丢弃的老毛病。
测试:重建 500 项
- test_engine 110 / test_actions 35 / test_server 131(skip 35) / test_tui_keys 75 /
test_bot 36 / test_client 113。未安装的第三方依赖用 skipUnless 自动跳过,其余照跑,
所以是全绿而不是靠跳过伪装。
- 被测试逼出两处真隐患并修掉:RemoteFarm._normalize() 必须「先补 scheme 再削尾斜杠」
(顺序反了畸形端点会溜到系统代理、报一个和真实原因无关的 502);StateView 空视图要
预置 plots/animals,否则连不上服务器时读 state.plots 直接 AttributeError。
优化:先量后改,依据与前后数字都在 tools/bench.py
- 新增 engine.brief_source():brief/summary/动作回执只要十几个标量,不再为它们序列化
整局;to_dict 里 net_worth 只算一次。brief -76%、summary -77%、leaderboard(32) -78%、
GET /api/world -31%、POST /api/actions?full=1 -16%。
- 拆掉三个巨型函数:create_app 475->32 行(_Services + _register_*)、
build_parser 与 available_actions 分组、__main__.main 160->7 行。
- 消除重复:等级门槛 5 处 -> _is_locked/_require_level/_newly_unlocked;hub 的
broadcast/broadcast_all/close_all -> _push_many;daemon.probe/world -> _get_json;
main.py 的 _add_endpoint_args/_leaderboard_lines。
- tools/bench.py 作为长期性能守卫(含调用计数器),谁把轻量路径改回 snapshot() 会立刻报警。
门禁:ruff format/check 全绿、pyright 0 错误、500 项测试 OK(skip 35)。
|
2026-09-19 12:55:45 +08:00 |
|
snowy-yang
|
fcd4cb4649
|
style: 引入 ruff,474 项告警清零
配置见 pyproject.toml:
- line-length=100:贴着现有代码定。若用默认的 88 会重排 122 行,
噪音淹没真正有价值的修复。
- 启用 E/W/F/I/UP/B/C4/SIM/RET/PERF/RUF;**不**启用 PTH。因为本项目的路径
多是"字符串身份"——要塞进 subprocess 命令行、写进 JSON 存档、打印给用户,
全换 pathlib 只是包一层再拆一层,换来 70 处纯风格改动。
- 忽略 RUF001/002/003:中文全角标点是正确写法,不是"歧义字符"。
- 忽略 PERF203:剩下两处都是"逐项容错"语义(逐个牧场存档要立刻上报失败、
收获全部要跳过失败地块)。把 try 提到循环外会毁掉"一项失败不影响其它项"
这个行为,是错的优化。
自动修复 358 项:Dict/List/Optional 换成 PEP 585/604 写法、导入排序、
失效的 noqa、空占位 f-string 等。
手工修复的(这些是真问题,不是风格偏好):
- B904 x9:except 中 raise 补异常链。刻意区分——网络/IO 失败用 from exc 保留
根因;参数解析失败用 from None,因为底层 ValueError 对玩家只是噪音。
- RUF012 x2:StateView 的 plots/animals 原本是**类级共享可变列表**,
改成在 __init__ 里初始化(apply({}) 会提前返回,所以必须显式摆好)。
- F841:TUI 里一个赋值后再没用过的死变量。
- SIM105 x11:try/except/pass 统一改成 contextlib.suppress。
- SIM115:日志句柄改用 with。Popen 已把 fd 复制给子进程,父进程可安全关闭。
- E501 x7:拆超长行;main.py 那处顺手提取变量,避免超长嵌套表达式。
- E741:把 l/r 改成 left_line/right_line。
- 其余:RUF046 / PERF401 / RET504 / RUF005 / UP031 / B007。
ruff format 重排 24 个文件。
验证:97 项测试全绿。
|
2026-09-18 21:18:22 +08:00 |
|
snowy-yang
|
a7725f8812
|
chore: 工程化改造前的基线快照
在引入 uv 依赖管理、ruff 静态检查与格式化、pyright 类型检查之前,
先把当前可正常运行的状态固定下来,作为后续大规模自动改动的回滚点。
此时状态:97 项测试全绿,HTTP + WebSocket 多牧场服务器可用。
|
2026-09-18 21:12:40 +08:00 |
|