Files
SimpleFarm/.workbuddy/memory/2026-09-25.md
T
snowy-yang a9bc8238ab 序列化热路径去重:to_dict 里重复求值的属性改局部复用
为什么:snapshot() 同时压在 SSE 每 250ms 推送和 full=1 动作回执两条
高频路径上,而它的成本大头是重复求值——profile 显示单次快照
1,128,001 次函数调用里近半是属性链重复计算同一份配置数据。

- Plot.to_dict:grow_days 原来经 4 个属性各求值一遍(本体 + days_left
  + is_ripe + stage),16 块地就是 64 次配置表查询。改为算一次局部
  复用;stage 的实现体拆到 _stage(grow),规则本体仍只有一份。
- Animal.to_dict:info 配置表原来查 4 次、cycle int() 两遍,改取一次。
- GameState.to_dict:weather_info 3 键 2 次查询;granary_free 原来把
  granary_capacity 和 storage_used 整个重算,改算一轮复用。

行为零变化(规则本体全在原属性里,532 项测试全绿)。
基准(tools/bench.py --repeat 5,最坏形态 16 地 + 8 牲畜):

    场景                       优化前      优化后
    engine.snapshot()          50.1 µs     44.2 µs      -12%
    session.snapshot()         51.0 µs     41.9 µs      -18%
    build_state_message()      50.9 µs     43.3 µs      -15%
    engine.available_actions   52.7 µs     48.7 µs       -8%

单次 snapshot 函数调用数 1,128,001 → 588,001(-48%),本次数字已记入
bench.py docstring 作长期参考。重复计数器全 OK(to_dict/snapshot/
net_worth 每请求仍各 1 次,无回归)。
2026-09-26 11:08:14 +08:00

82 lines
5.7 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-25 四座自动化设施
- 用户要「添加自动化设施」,AskUserQuestion 给了 4 个候选,用户全选:
喷灌系统 / 自动收割机 / 自动售货机 / 饲料磨坊。
- 设计上沿用既有建筑哲学:**只替代行动点动作,绝不自动花钱**。
`_run_buildings` 扩成六段流水线(顺序即依赖):
水井 → 喷灌 → 收割机 → 磨坊 → 喂食器 → 售货机。
磨坊刻意排在喂食器前——早上磨的饲料当天就能喂上;
收割机遇满仓停摆、作物留地(同喂食器缺料停摆)。
- 数值:喷灌 2800/限1/全量浇水;收割机 900/限3/每天各收 2 块熟地、
**刻意不给经验**(机器收的不算劳动,给手动玩法留价值);
售货机 1200/限1/市价>基准价才卖、每天上限 5 份、越贵越先卖
(_earn 复用 sell 的入账逻辑);磨坊 500/限1/1 份最便宜的作物磨 3 份饲料
(FEED_UNIT_PRICE=6,小麦 14 磨 18 金的料,微赚但吃掉作物)。
- 架构红利:config.BUILDINGS 驱动一切(build 动作、catalog、网页建筑区、
可用动作推荐),新增建筑 engine 之外零改动。
- 测试 +6,途中确认两个既有行为:引擎自带 START_FEED=5(feed 断言要先清零);
advance_day 牲畜阶段末尾会把 fed 复位(「喂到了」的证据是 pending 累计,
鸡 cycle=1 当天喂到就 pending=1)。
- 门禁 534 项 OK(skip 39)、ruff/pyright 全绿;提交 cb4c1e7 已推送。
服务器已重启到新代码,catalog 8 座建筑齐全。
## 2026-09-25 Canvas 二维农场地图
- 用户问「能不能做成二维的」,AskUserQuestion 三选一:CSS 农田视图 /
Canvas 像素农场 / 网页+TUI 都改,用户选 **Canvas 像素农场**。
- 实现:全在 webui.py(服务端零改动,state 里现成的 plots/animals/buildings
够用)。页首插一张全宽 canvas 卡片:逻辑坐标绘制(456×152 起,随地块数变)、
实际像素 = 容器宽 × dpr,命中检测自己算(逻辑坐标 = clientX × MAP.w/rect.width)。
农田 4 列方阵(>12 块改 6 列)土壤颜色随水分插值、作物 emoji 随生长放大、
成熟金光、干裂画裂纹;牧场围栏里牲畜随机漫步回弹、待收产物金泡、没喂 💢;
底部建筑带(owned>0 才画);天气五種各有天色+雨滴/云/烈日。
交互复用 openCropPick/act():空地点击弹选种、熟地收获、缺水浇水、
牲畜收取优先于喂食。下方卡片面板全保留(地图负责沉浸,卡片负责精确)。
- **踩坑:rAF 循环首帧同步执行**。updateMap 原顺序 bind→layout,首帧
`f.ts` 在 MAP.field=null 上 TypeError,把 requestAnimationFrame 注册一起吞掉,
表现是「画布全白但点击命中正常」(命中是纯数学不经过画布)。
修复:layout→sync→bind→fit 顺序 + mapStep/mapDraw 空值护栏。
排查手法:截图空白先别信,`getImageData` 读像素 + 探针 rAF 计数 + 云位移
三连定位「画了没」和「循环活着没」。
- 真机验收:登录、canvas 点击浇水(AP 4→3、水分 2→3、toast)、
悬停 tooltip、截图目检全过;errors/console 干净。534 项测试全绿。
## 2026-09-25 移除行动点系统
- 用户拍板:「谁家浇水还要体力啊 体力删掉 都已经有时间了」——时间
(40 分钟一天、只能自然流动)本身就是唯一节奏阀门,AP 是重复惩罚。
- 连根拔的触点:config(START_AP/AP_COST)、models(ap/max_ap 字段,旧档
payload 的 ap 键 from_dict 自然忽略)、engine(_spend_ap + 8 调用点 +
每日恢复)、bot(AP 分支)、client/TUI/main/README/规则文本。
- **SQLite 老库迁移**:ap 是标量 NOT NULL 列,connect() 里
`PRAGMA table_info` 发现即 `ALTER TABLE farms DROP COLUMN ap`
(Python 3.13 的 SQLite ≥3.35 支持)。实测 saves/farm.db 自动迁完。
- 网页端:删"行动点"统计卡;"下一天"倒计时改**客户端本地走秒**
(render 记 tickLeft/tickStamp 基点,1s 定时器补 SSE 推送间隙);
字体栈补 MiSans/HarmonyOS Sans/PingFang、正文 15px、数字
tabular-nums、canvas 小字加粗。
- 测试 -2(AP 专属用例没了)改 5,532 项全绿;提交 15d290a 已推送。
真机:老库迁移 ✅、无行动点卡 ✅、倒计时 39:04→39:02 走秒 ✅。
## 2026-09-25 地图手感打磨(4bc7729)
- **动作粒子**:MAP.fx 数组,act() 成功后 mapJuice(action, params)——
地块号/牲畜 id 反查画布坐标喷粒子(重力+寿命衰减)+ 上浮表情浮字
(浇水💧播种🌱收获✨喂食🍽)。api() 失败返回 null 天然不触发。
- **昼夜色温**:dayTint(prog) 按一天进度叠半透明层(清晨淡金→正午无→
黄昏橙→入夜靛紫)。tick_seconds 只在完整快照里、brief 没有,
靠 refreshFromBrief 的 Object.assign 合并语义存活,兜底 2400。
- 成熟金光改呼吸式(sin 起伏 shadowBlur);悬停可交互对象指针变手型。
- 验收注意:粒子视觉链路可用 mapJuice 直接注入测(真动作要看地块状态
凑巧——全满时浇水会走失败路径,正好验证"失败不触发")。
- **沙箱进程回收边界(重要)**:WorkBuddy 会话里启动的进程都会被环境
回收——后台任务(跑 15m~1h 后静默死、日志无错误)、甚至
`--daemon` 的 DETACHED_PROCESS 守护进程(沙箱 Job 对象 kill-on-close
连 breakaway 进程一起杀)都活不长。**长期挂机必须让用户自己在系统
终端跑** `uv run main.py serve`(或 `--daemon`),那才是真正独立的进程。
会话内只做临时拉起 + 自动存档(60s 周期)兜底损失。