=== ruff 版本 ===
ruff 0.16.8

=== ruff check --statistics ===
225	UP006  	[*] non-pep585-annotation
 54	UP045  	[*] non-pep604-annotation-optional
 32	UP035  	[-] deprecated-import
 23	E501   	[ ] line-too-long
 18	PTH120 	[ ] os-path-dirname
 15	PTH118 	[ ] os-path-join
 12	B904   	[ ] raise-without-from-inside-except
 12	SIM105 	[ ] suppressible-exception
 11	PTH123 	[ ] builtin-open
  8	PTH100 	[ ] os-path-abspath
  7	PTH103 	[ ] os-makedirs
  6	RUF100 	[*] unused-noqa
  5	PTH110 	[ ] os-path-exists
  5	UP015  	[*] redundant-open-modes
  5	PERF203	[ ] try-except-in-loop
  5	RUF023 	[*] unsorted-dunder-slots
  3	PTH107 	[ ] os-remove
  3	UP037  	[*] quoted-annotation
  3	RUF046 	[ ] unnecessary-cast-to-int
  3	I001   	[*] unsorted-imports
  2	F541   	[*] f-string-missing-placeholders
  2	PERF401	[ ] manual-list-comprehension
  2	RUF012 	[ ] mutable-class-default
  2	UP007  	[*] non-pep604-annotation-union
  2	F401   	[*] unused-import
  1	E741   	[ ] ambiguous-variable-name
  1	RUF005 	[ ] collection-literal-concatenation
  1	SIM115 	[ ] open-file-with-context-handler
  1	PTH105 	[ ] os-replace
  1	UP031  	[ ] printf-string-formatting
  1	RET504 	[ ] unnecessary-assign
  1	RUF022 	[*] unsorted-dunder-all
  1	B007   	[ ] unused-loop-control-variable
  1	F841   	[ ] unused-variable
Found 474 errors.
[*] 312 fixable with the `--fix` option (17 hidden fixes can be enabled with the `--unsafe-fixes` option).

=== ruff format --check（哪些文件格式不合规） ===
unformatted: File would be reformatted
   --> README.md:260:1
    |
259 |
260 +
261 | async def main():
--------------------------------------------------------------------------------
272 |
273 +
274 | asyncio.run(main())
    |

unformatted: File would be reformatted
  --> ai_player.py:53:29
   |
52 |     try:
   -         client = RemoteFarm(endpoint=args.url, farm=args.farm, token=args.token,
   -                             name=f"bot-{args.farm}", realtime=False)
53 +         client = RemoteFarm(
54 +             endpoint=args.url,
55 +             farm=args.farm,
56 +             token=args.token,
57 +             name=f"bot-{args.farm}",
58 +             realtime=False,
59 +         )
60 |     except GameError as exc:
--------------------------------------------------------------------------------
65 |     snapshot = client.snapshot()
   -     print(f"已连接 {args.url} · 牧场「{args.farm}」· 第 {snapshot.get('day')} 天 · "
   -           f"金币 {snapshot.get('gold')}")
66 +     print(
67 +         f"已连接 {args.url} · 牧场「{args.farm}」· 第 {snapshot.get('day')} 天 · "
68 +         f"金币 {snapshot.get('gold')}"
69 +     )
70 |
--------------------------------------------------------------------------------
76 |         bot.stop()
   -         summary = {"name": args.farm, "actions": bot.actions_executed,
   -                    "failures": bot.failures, "interrupted": True}
77 +         summary = {
78 +             "name": args.farm,
79 +             "actions": bot.actions_executed,
80 +             "failures": bot.failures,
81 +             "interrupted": True,
82 +         }
83 |
   |

unformatted: File would be reformatted
  --> farm\__init__.py:18:14
   |
17 | __all__ = [
   -     "config", "models", "engine", "storage", "keys", "tui",
   -     "client", "bot", "server",
18 +     "config",
19 +     "models",
20 +     "engine",
21 +     "storage",
22 +     "keys",
23 +     "tui",
24 +     "client",
25 +     "bot",
26 +     "server",
27 | ]
   |

unformatted: File would be reformatted
   --> farm\bot.py:30:18
    |
29  |
    -     def __init__(self, client, verbose: bool = True, pause: float = 0.25,
    -                  name: str = "bot") -> None:
    -         self.client = client          # 任何与 RemoteFarm 接口一致的对象
30  +     def __init__(
31  +         self, client, verbose: bool = True, pause: float = 0.25, name: str = "bot"
32  +     ) -> None:
33  +         self.client = client  # 任何与 RemoteFarm 接口一致的对象
34  |         self.verbose = verbose
--------------------------------------------------------------------------------
54  |     # ------------------------------------------------------------ 打分
    -     def score(self, action: Dict[str, Any], state: Dict[str, Any],
    -               catalog: Dict[str, Any]) -> float:
55  +     def score(
56  +         self, action: Dict[str, Any], state: Dict[str, Any], catalog: Dict[str, Any]
57  +     ) -> float:
58  |         """给一个候选动作打分。分数越高越先执行。
--------------------------------------------------------------------------------
94  |         if name == "buy_animal":
    -             info = next((a for a in catalog.get("animals", [])
    -                          if a["key"] == params.get("kind")), None)
95  +             info = next(
96  +                 (a for a in catalog.get("animals", []) if a["key"] == params.get("kind")), None
97  +             )
98  |             cost = info["cost"] if info else 0
--------------------------------------------------------------------------------
124 |         try:
    -             result = self.client._action(name, params)   # 走通用动作入口
125 +             result = self.client._action(name, params)  # 走通用动作入口
126 |         except GameError as exc:
--------------------------------------------------------------------------------
156 |     # ------------------------------------------------------------ 主循环
    -     def run(self, rounds: int = 0, max_minutes: float = 0.0,
    -             idle_limit: int = 8) -> Dict[str, Any]:
157 +     def run(self, rounds: int = 0, max_minutes: float = 0.0, idle_limit: int = 8) -> Dict[str, Any]:
158 |         """跑机器人。
--------------------------------------------------------------------------------
165 |         round_no = 0
    -         self.say(f"机器人 {self.name} 上线（第 {start_day} 天，"
    -                  f"{self.client.transport_label()}）")
166 +         self.say(f"机器人 {self.name} 上线（第 {start_day} 天，{self.client.transport_label()}）")
167 |
--------------------------------------------------------------------------------
202 |         }
    -         self.say(f"机器人 {self.name} 下线：跑了 {summary['days']} 天、"
    -                  f"执行 {summary['actions']} 个动作、净资产 {summary['net_worth']}")
203 +         self.say(
204 +             f"机器人 {self.name} 下线：跑了 {summary['days']} 天、"
205 +             f"执行 {summary['actions']} 个动作、净资产 {summary['net_worth']}"
206 +         )
207 |         return summary
--------------------------------------------------------------------------------
220 |
    -     bot.choose = choose          # type: ignore[assignment]
221 +     bot.choose = choose  # type: ignore[assignment]
222 |     bot.name = kwargs.get("name", "random")
    |

unformatted: File would be reformatted
   --> farm\client.py:234:18
    |
233 |
    -     def __init__(self, endpoint: str = DEFAULT_ENDPOINT, farm: str = "default",
    -                  token: Optional[str] = None, name: str = "", timeout: float = 6.0,
    -                  realtime: bool = True, autostart: bool = True) -> None:
234 +     def __init__(
235 +         self,
236 +         endpoint: str = DEFAULT_ENDPOINT,
237 +         farm: str = "default",
238 +         token: Optional[str] = None,
239 +         name: str = "",
240 +         timeout: float = 6.0,
241 +         realtime: bool = True,
242 +         autostart: bool = True,
243 +     ) -> None:
244 |         self.endpoint = self._normalize(endpoint)
--------------------------------------------------------------------------------
252 |         self.connected = False
    -         self.mode = "http"          # http / ws / poll
253 +         self.mode = "http"  # http / ws / poll
254 |         self.last_error = ""
--------------------------------------------------------------------------------
265 |         self._ws_loop: Any = None
    -         self._catalog: Dict[str, Any] = {"crops": [], "animals": [],
    -                                          "feed_price": config.FEED_UNIT_PRICE,
    -                                          "water_capacity": config.WATER_CAPACITY,
    -                                          "ap_cost": config.AP_COST}
266 +         self._catalog: Dict[str, Any] = {
267 +             "crops": [],
268 +             "animals": [],
269 +             "feed_price": config.FEED_UNIT_PRICE,
270 +             "water_capacity": config.WATER_CAPACITY,
271 +             "ap_cost": config.AP_COST,
272 +         }
273 |         self._log: Deque[Dict[str, Any]] = deque(maxlen=LOG_KEEP)
--------------------------------------------------------------------------------
286 |         if endpoint.startswith("tcp://"):
    -             endpoint = "http://" + endpoint[len("tcp://"):]
287 +             endpoint = "http://" + endpoint[len("tcp://") :]
288 |         if not endpoint.startswith(("http://", "https://")):
--------------------------------------------------------------------------------
302 |
    -     def _http(self, method: str, path: str, payload: Optional[Dict[str, Any]] = None,
    -               **params: Any) -> Dict[str, Any]:
303 +     def _http(
304 +         self, method: str, path: str, payload: Optional[Dict[str, Any]] = None, **params: Any
305 +     ) -> Dict[str, Any]:
306 |         url = self.url(path, **params)
--------------------------------------------------------------------------------
326 |                 "network",
    -                 f"连不上服务器 {self.endpoint}（{exc}）。"
    -                 "请先启动：python main.py serve --daemon",
327 +                 f"连不上服务器 {self.endpoint}（{exc}）。请先启动：python main.py serve --daemon",
328 |                 status=503,
--------------------------------------------------------------------------------
415 |                     self._log_seq = max(self._log_seq, seq)
    -             self._log = deque(sorted(self._log, key=lambda e: int(e.get("seq", 0))),
    -                               maxlen=LOG_KEEP)
416 +             self._log = deque(
417 +                 sorted(self._log, key=lambda e: int(e.get("seq", 0))), maxlen=LOG_KEEP
418 +             )
419 |
420 |     # ------------------------------------------------------------ 写接口
    -     def _action(self, action: str, params: Optional[Dict[str, Any]] = None,
    -                 transport: str = "auto") -> Dict[str, Any]:
421 +     def _action(
422 +         self, action: str, params: Optional[Dict[str, Any]] = None, transport: str = "auto"
423 +     ) -> Dict[str, Any]:
424 |         """执行一个动作。
--------------------------------------------------------------------------------
430 |         params = params or {}
    -         use_ws = (transport == "ws") or (transport == "auto" and self.mode == "ws"
    -                                          and self.connected)
431 +         use_ws = (transport == "ws") or (
432 +             transport == "auto" and self.mode == "ws" and self.connected
433 +         )
434 |         if use_ws:
--------------------------------------------------------------------------------
451 |         # full=1：让服务器顺手把完整快照带回来，省掉"操作后再查一次"
    -         data = self._http("POST", "/api/actions", {"action": action, "params": params},
    -                           full=1)
452 +         data = self._http("POST", "/api/actions", {"action": action, "params": params}, full=1)
453 |         self._apply(data.get("snapshot") or data.get("state") or {})
--------------------------------------------------------------------------------
528 |         self._stop.clear()
    -         self._thread = threading.Thread(target=self._realtime_loop, name="farm-ws",
    -                                         daemon=True)
529 +         self._thread = threading.Thread(target=self._realtime_loop, name="farm-ws", daemon=True)
530 |         self._thread.start()
--------------------------------------------------------------------------------
599 |     async def _ws_main(self, websockets) -> None:
    -         async with websockets.connect(self.ws_url, open_timeout=self.timeout,
    -                                       ping_interval=20, ping_timeout=20,
    -                                       max_size=4 * 1024 * 1024) as socket:
600 +         async with websockets.connect(
601 +             self.ws_url,
602 +             open_timeout=self.timeout,
603 +             ping_interval=20,
604 +             ping_timeout=20,
605 +             max_size=4 * 1024 * 1024,
606 +         ) as socket:
607 |             self.mode = "ws"
--------------------------------------------------------------------------------
667 |                 self._merge_log(message.get("log") or [])
    -                 future.set_result({
    -                     "ok": True,
    -                     "message": message.get("message", "完成"),
    -                     **(message.get("result") or {}),
    -                     "state": message.get("state"),
    -                     "snapshot": message.get("snapshot"),
    -                     "log": message.get("log"),
    -                 })
668 +                 future.set_result(
669 +                     {
670 +                         "ok": True,
671 +                         "message": message.get("message", "完成"),
672 +                         **(message.get("result") or {}),
673 +                         "state": message.get("state"),
674 +                         "snapshot": message.get("snapshot"),
675 +                         "log": message.get("log"),
676 +                     }
677 +                 )
678 |             else:
    -                 future.set_exception(GameError(message.get("error", "error"),
    -                                                message.get("message", "操作失败")))
679 +                 future.set_exception(
680 +                     GameError(message.get("error", "error"), message.get("message", "操作失败"))
681 +                 )
682 |             return
683 |
684 |         if kind == "day":
    -             self._merge_log([{
    -                 "seq": self._log_seq + 1,
    -                 "day": message.get("day", 0),
    -                 "kind": "info",
    -                 "text": f"第 {message.get('day')} 天开始",
    -             }] + [{
    -                 "seq": self._log_seq + 2 + i,
    -                 "day": message.get("day", 0),
    -                 "kind": "warn",
    -                 "text": text,
    -             } for i, text in enumerate(message.get("events") or [])])
685 +             self._merge_log(
686 +                 [
687 +                     {
688 +                         "seq": self._log_seq + 1,
689 +                         "day": message.get("day", 0),
690 +                         "kind": "info",
691 +                         "text": f"第 {message.get('day')} 天开始",
692 +                     }
693 +                 ]
694 +                 + [
695 +                     {
696 +                         "seq": self._log_seq + 2 + i,
697 +                         "day": message.get("day", 0),
698 +                         "kind": "warn",
699 +                         "text": text,
700 +                     }
701 +                     for i, text in enumerate(message.get("events") or [])
702 +                 ]
703 +             )
704 |             return
--------------------------------------------------------------------------------
716 |
    -     def _ws_action(self, action: str, params: Dict[str, Any],
    -                    timeout: Optional[float] = None) -> Dict[str, Any]:
717 +     def _ws_action(
718 +         self, action: str, params: Dict[str, Any], timeout: Optional[float] = None
719 +     ) -> Dict[str, Any]:
720 |         with self._lock:
    |

unformatted: File would be reformatted
   --> farm\config.py:15:19
    |
14  |
    - START_GOLD = 120          # 初始金币
    - START_LEVEL = 1           # 初始等级
    - START_AP = 10             # 每天行动点上限
    - START_FEED = 5            # 初始饲料库存
15  + START_GOLD = 120  # 初始金币
16  + START_LEVEL = 1  # 初始等级
17  + START_AP = 10  # 每天行动点上限
18  + START_FEED = 5  # 初始饲料库存
19  |
    - BASE_PLOTS = 6            # 初始土地数量
    - MAX_PLOTS = 16            # 土地数量硬上限
    - PLOT_BASE_PRICE = 200     # 第一块新地的价格
20  + BASE_PLOTS = 6  # 初始土地数量
21  + MAX_PLOTS = 16  # 土地数量硬上限
22  + PLOT_BASE_PRICE = 200  # 第一块新地的价格
23  | PLOT_PRICE_GROWTH = 1.75  # 每多买一块，价格乘以该系数
24  |
    - FEED_UNIT_PRICE = 6       # 主动购买饲料的单价
25  + FEED_UNIT_PRICE = 6  # 主动购买饲料的单价
26  | FEED_EMERGENCY_PRICE = 9  # 自动补货（缺饲料时喂食）的惩罚单价
    - FEED_BULK = 10            # 一次购买饲料的默认批量
27  + FEED_BULK = 10  # 一次购买饲料的默认批量
28  |
    - WATER_CAPACITY = 3        # 一次浇水可支撑的天数
29  + WATER_CAPACITY = 3  # 一次浇水可支撑的天数
30  | PLANT_INITIAL_MOISTURE = 2  # 播种后地块自带的水分
    - PRICE_VOLATILITY = 0.18   # 每天价格波动幅度（±18%）
    - LUCKY_CHANCE = 0.12       # 丰收暴击概率（产出翻倍）
    - MAX_LOG_ENTRIES = 300     # 内存中保留的事件条数上限
    - MAX_FEED_PURCHASE = 200   # 单次购买饲料的数量上限
31  + PRICE_VOLATILITY = 0.18  # 每天价格波动幅度（±18%）
32  + LUCKY_CHANCE = 0.12  # 丰收暴击概率（产出翻倍）
33  + MAX_LOG_ENTRIES = 300  # 内存中保留的事件条数上限
34  + MAX_FEED_PURCHASE = 200  # 单次购买饲料的数量上限
35  |
--------------------------------------------------------------------------------
50  |
51  +
52  | # 经验曲线：升到 level+1 所需经验
--------------------------------------------------------------------------------
56  |
57  +
58  | # 升级奖励金币
--------------------------------------------------------------------------------
76  |     "wheat": {
    -         "name": "小麦", "seed_cost": 5, "grow_days": 2,
    -         "base_price": 14, "exp": 3, "level_req": 1,
77  +         "name": "小麦",
78  +         "seed_cost": 5,
79  +         "grow_days": 2,
80  +         "base_price": 14,
81  +         "exp": 3,
82  +         "level_req": 1,
83  |     },
84  |     "carrot": {
    -         "name": "胡萝卜", "seed_cost": 8, "grow_days": 3,
    -         "base_price": 26, "exp": 5, "level_req": 1,
85  +         "name": "胡萝卜",
86  +         "seed_cost": 8,
87  +         "grow_days": 3,
88  +         "base_price": 26,
89  +         "exp": 5,
90  +         "level_req": 1,
91  |     },
92  |     "tomato": {
    -         "name": "番茄", "seed_cost": 16, "grow_days": 4,
    -         "base_price": 52, "exp": 9, "level_req": 2,
93  +         "name": "番茄",
94  +         "seed_cost": 16,
95  +         "grow_days": 4,
96  +         "base_price": 52,
97  +         "exp": 9,
98  +         "level_req": 2,
99  |     },
100 |     "corn": {
    -         "name": "玉米", "seed_cost": 28, "grow_days": 6,
    -         "base_price": 95, "exp": 14, "level_req": 3,
101 +         "name": "玉米",
102 +         "seed_cost": 28,
103 +         "grow_days": 6,
104 +         "base_price": 95,
105 +         "exp": 14,
106 +         "level_req": 3,
107 |     },
108 |     "pumpkin": {
    -         "name": "南瓜", "seed_cost": 45, "grow_days": 8,
    -         "base_price": 180, "exp": 22, "level_req": 4,
109 +         "name": "南瓜",
110 +         "seed_cost": 45,
111 +         "grow_days": 8,
112 +         "base_price": 180,
113 +         "exp": 22,
114 +         "level_req": 4,
115 |     },
116 |     "strawberry": {
    -         "name": "草莓", "seed_cost": 70, "grow_days": 10,
    -         "base_price": 320, "exp": 35, "level_req": 5,
117 +         "name": "草莓",
118 +         "seed_cost": 70,
119 +         "grow_days": 10,
120 +         "base_price": 320,
121 +         "exp": 35,
122 +         "level_req": 5,
123 |     },
--------------------------------------------------------------------------------
149 |     "chicken": {
    -         "name": "鸡", "cost": 90, "product": "鸡蛋",
    -         "base_price": 15, "cycle": 1, "feed_per_day": 1,
    -         "exp": 4, "level_req": 1,
150 +         "name": "鸡",
151 +         "cost": 90,
152 +         "product": "鸡蛋",
153 +         "base_price": 15,
154 +         "cycle": 1,
155 +         "feed_per_day": 1,
156 +         "exp": 4,
157 +         "level_req": 1,
158 |     },
159 |     "sheep": {
    -         "name": "羊", "cost": 220, "product": "羊毛",
    -         "base_price": 55, "cycle": 2, "feed_per_day": 1,
    -         "exp": 9, "level_req": 2,
160 +         "name": "羊",
161 +         "cost": 220,
162 +         "product": "羊毛",
163 +         "base_price": 55,
164 +         "cycle": 2,
165 +         "feed_per_day": 1,
166 +         "exp": 9,
167 +         "level_req": 2,
168 |     },
169 |     "cow": {
    -         "name": "牛", "cost": 480, "product": "牛奶",
    -         "base_price": 72, "cycle": 1, "feed_per_day": 2,
    -         "exp": 15, "level_req": 3,
170 +         "name": "牛",
171 +         "cost": 480,
172 +         "product": "牛奶",
173 +         "base_price": 72,
174 +         "cycle": 1,
175 +         "feed_per_day": 2,
176 +         "exp": 15,
177 +         "level_req": 3,
178 |     },
--------------------------------------------------------------------------------
190 | MAX_TICK_SECONDS = 600.0
    - SLEEP_COOLDOWN = 2.0         # 手动"休息"推进一天的冷却（秒）
191 + SLEEP_COOLDOWN = 2.0  # 手动"休息"推进一天的冷却（秒）
192 |
    |

unformatted: File would be reformatted
   --> farm\engine.py:80:36
    |
79  |                     "day": self.state.day,
    -                     "kind": kind,   # info / good / bad / warn / action
80  +                     "kind": kind,  # info / good / bad / warn / action
81  |                     "text": text,
--------------------------------------------------------------------------------
150 |                         "locked": self.state.level < info["level_req"],
    -                         "profit_per_day": round(
    -                             (price - feed_cost) / max(1, info["cycle"]), 2
    -                         ),
151 +                         "profit_per_day": round((price - feed_cost) / max(1, info["cycle"]), 2),
152 |                     }
--------------------------------------------------------------------------------
368 |             if plot.crop is not None:
    -                 raise GameError("plot_busy", f"{plot.index + 1} 号地已经种了{config.CROPS[plot.crop]['name']}")
369 +                 raise GameError(
370 +                     "plot_busy", f"{plot.index + 1} 号地已经种了{config.CROPS[plot.crop]['name']}"
371 +                 )
372 |
--------------------------------------------------------------------------------
428 |             lucky = "（丰收！）" if quantity > 1 else ""
    -             self.add_log(
    -                 f"收获 {quantity} 份{info['name']}{lucky}，+{earned} 金币", "good"
    -             )
429 +             self.add_log(f"收获 {quantity} 份{info['name']}{lucky}，+{earned} 金币", "good")
430 |             levelups = self._gain_exp(info["exp"])
--------------------------------------------------------------------------------
643 |                     if plot.is_ripe:
    -                         events.append(f"{plot.index + 1} 号地的{config.CROPS[plot.crop]['name']}已成熟")
644 +                         events.append(
645 +                             f"{plot.index + 1} 号地的{config.CROPS[plot.crop]['name']}已成熟"
646 +                         )
647 |                 else:
    |

unformatted: File would be reformatted
   --> farm\models.py:26:29
    |
25  |     def __init__(self, index: int) -> None:
    -         self.index = index          # 0-based 下标，对外展示用 index+1
26  +         self.index = index  # 0-based 下标，对外展示用 index+1
27  |         self.crop: Optional[str] = None
    -         self.growth: float = 0.0    # 已生长天数
    -         self.moisture: int = 0      # 剩余可支撑天数
28  +         self.growth: float = 0.0  # 已生长天数
29  +         self.moisture: int = 0  # 剩余可支撑天数
30  |         self.planted_day: int = 0
--------------------------------------------------------------------------------
104 |         self.kind = kind
    -         self.fed = False        # 今天是否已喂食
    -         self.progress = 0       # 距下次产出的累计天数
    -         self.starve = 0         # 连续未进食天数
    -         self.pending = 0        # 待收集的产出数量
105 +         self.fed = False  # 今天是否已喂食
106 +         self.progress = 0  # 距下次产出的累计天数
107 +         self.starve = 0  # 连续未进食天数
108 +         self.pending = 0  # 待收集的产出数量
109 |
--------------------------------------------------------------------------------
210 |             return -1
    -         return int(config.PLOT_BASE_PRICE * (config.PLOT_PRICE_GROWTH ** self.plots_bought))
211 +         return int(config.PLOT_BASE_PRICE * (config.PLOT_PRICE_GROWTH**self.plots_bought))
212 |
--------------------------------------------------------------------------------
231 |         land = sum(
    -             config.PLOT_BASE_PRICE * (config.PLOT_PRICE_GROWTH ** i)
    -             for i in range(self.plots_bought)
232 +             config.PLOT_BASE_PRICE * (config.PLOT_PRICE_GROWTH**i) for i in range(self.plots_bought)
233 |         )
--------------------------------------------------------------------------------
296 |             plots.extend(Plot(i) for i in range(len(plots), count))
    -         state.plots = plots[:config.MAX_PLOTS]
297 +         state.plots = plots[: config.MAX_PLOTS]
298 |         for i, plot in enumerate(state.plots):
    |

unformatted: File would be reformatted
  --> farm\net.py:46:14
   |
45 |
   - def open_url(target: Union[str, urllib.request.Request], timeout: float = 6.0,
   -              host: Optional[str] = None) -> Any:
46 + def open_url(
47 +     target: Union[str, urllib.request.Request], timeout: float = 6.0, host: Optional[str] = None
48 + ) -> Any:
49 |     """打开一个 URL 或 Request，回环地址自动绕过代理。
   |

unformatted: File would be reformatted
  --> farm\server\__init__.py:25:19
   |
24 | _LAZY = {
   -     "API_VERSION", "SERVER_NAME", "ServerConfig", "ServerHandle",
   -     "build_app", "create_app", "rules_text", "brief",
   -     "Action", "Param", "ACTIONS", "run_action", "describe_actions", "action_names",
   -     "FarmSession", "SessionManager", "FarmClock", "validate_farm_id", "DEFAULT_FARM",
   -     "Client", "ConnectionHub", "Publisher",
   -     "DaemonManager", "pid_alive",
   -     "DASHBOARD_HTML", "dashboard_text",
25 +     "API_VERSION",
26 +     "SERVER_NAME",
27 +     "ServerConfig",
28 +     "ServerHandle",
29 +     "build_app",
30 +     "create_app",
31 +     "rules_text",
32 +     "brief",
33 +     "Action",
34 +     "Param",
35 +     "ACTIONS",
36 +     "run_action",
37 +     "describe_actions",
38 +     "action_names",
39 +     "FarmSession",
40 +     "SessionManager",
41 +     "FarmClock",
42 +     "validate_farm_id",
43 +     "DEFAULT_FARM",
44 +     "Client",
45 +     "ConnectionHub",
46 +     "Publisher",
47 +     "DaemonManager",
48 +     "pid_alive",
49 +     "DASHBOARD_HTML",
50 +     "dashboard_text",
51 | }
--------------------------------------------------------------------------------
55 |
   - def __getattr__(name: str) -> Any:   # PEP 562：模块级惰性属性
56 + def __getattr__(name: str) -> Any:  # PEP 562：模块级惰性属性
57 |     if name not in _LAZY:
--------------------------------------------------------------------------------
64 |         module = import_module("farm.server.webui")
   -     elif name in ("Action", "Param", "ACTIONS", "run_action", "describe_actions",
   -                   "action_names"):
65 +     elif name in ("Action", "Param", "ACTIONS", "run_action", "describe_actions", "action_names"):
66 |         module = import_module("farm.server.actions")
   -     elif name in ("FarmSession", "SessionManager", "FarmClock", "validate_farm_id",
   -                   "DEFAULT_FARM"):
67 +     elif name in ("FarmSession", "SessionManager", "FarmClock", "validate_farm_id", "DEFAULT_FARM"):
68 |         module = import_module("farm.server.session")
   |

unformatted: File would be reformatted
   --> farm\server\__main__.py:23:25
    |
22  |     )
    -     parser.add_argument("--host", default="127.0.0.1",
    -                         help="监听地址，默认 127.0.0.1；局域网联机用 0.0.0.0")
23  +     parser.add_argument(
24  +         "--host", default="127.0.0.1", help="监听地址，默认 127.0.0.1；局域网联机用 0.0.0.0"
25  +     )
26  |     parser.add_argument("--port", type=int, default=8765, help="监听端口，默认 8765")
27  |     parser.add_argument("--token", default=None, help="访问令牌，设置后所有接口都要带")
    -     parser.add_argument("--tick", type=float, default=config.DEFAULT_TICK_SECONDS,
    -                         help=f"每多少秒推进一天，默认 {config.DEFAULT_TICK_SECONDS:.0f} 秒")
28  +     parser.add_argument(
29  +         "--tick",
30  +         type=float,
31  +         default=config.DEFAULT_TICK_SECONDS,
32  +         help=f"每多少秒推进一天，默认 {config.DEFAULT_TICK_SECONDS:.0f} 秒",
33  +     )
34  |     parser.add_argument("--save-dir", default=None, help="存档目录，默认 <项目根>/saves")
    -     parser.add_argument("--save", dest="save_file", default=None,
    -                         help="兼容旧参数：指定 default 牧场的存档文件")
35  +     parser.add_argument(
36  +         "--save", dest="save_file", default=None, help="兼容旧参数：指定 default 牧场的存档文件"
37  +     )
38  |     parser.add_argument("--max-sessions", type=int, default=64, help="同时在线的牧场上限")
    -     parser.add_argument("--idle-timeout", type=float, default=1800.0,
    -                         help="无人访问多少秒后把牧场移出内存（存档保留），默认 1800")
39  +     parser.add_argument(
40  +         "--idle-timeout",
41  +         type=float,
42  +         default=1800.0,
43  +         help="无人访问多少秒后把牧场移出内存（存档保留），默认 1800",
44  +     )
45  |     parser.add_argument("--world", default="SimpleFarm 公共农场", help="世界名称")
    -     parser.add_argument("--log-level", default="info",
    -                         choices=["critical", "error", "warning", "info", "debug"])
46  +     parser.add_argument(
47  +         "--log-level", default="info", choices=["critical", "error", "warning", "info", "debug"]
48  +     )
49  |     parser.add_argument("--access-log", action="store_true", help="打印每个 HTTP 请求")
    -     parser.add_argument("--no-remote-control", action="store_true",
    -                         help="禁止通过接口停机")
    -     parser.add_argument("--daemon", action="store_true",
    -                         help="转到后台运行（等价于 python main.py serve --daemon）")
    -     parser.add_argument("--pid-file", default=None,
    -                         help="把本进程 pid 写到该文件（守护进程模式会自动指定）")
50  +     parser.add_argument("--no-remote-control", action="store_true", help="禁止通过接口停机")
51  +     parser.add_argument(
52  +         "--daemon", action="store_true", help="转到后台运行（等价于 python main.py serve --daemon）"
53  +     )
54  +     parser.add_argument(
55  +         "--pid-file", default=None, help="把本进程 pid 写到该文件（守护进程模式会自动指定）"
56  +     )
57  |     parser.add_argument("--log-file", default=DEFAULT_LOG_FILE, help=argparse.SUPPRESS)
--------------------------------------------------------------------------------
82  |         manager = DaemonManager(
    -             host=args.host, port=args.port, token=args.token,
    -             tick=clamp_tick(args.tick), save_dir=save_dir,
83  +             host=args.host,
84  +             port=args.port,
85  +             token=args.token,
86  +             tick=clamp_tick(args.tick),
87  +             save_dir=save_dir,
88  |             pid_file=args.pid_file or DEFAULT_PID_FILE,
--------------------------------------------------------------------------------
101 |         import websockets  # noqa: F401  只是探测，uvicorn 需要它来做 WS 协议升级
102 +
103 |         ws_ok = True
--------------------------------------------------------------------------------
153 |     if not ws_ok:
    -         print("  ⚠ 未检测到 websockets 库，/ws 实时通道可能不可用；"
    -               "网页端会自动降级为轮询。安装：pip install 'uvicorn[standard]'")
154 +         print(
155 +             "  ⚠ 未检测到 websockets 库，/ws 实时通道可能不可用；"
156 +             "网页端会自动降级为轮询。安装：pip install 'uvicorn[standard]'"
157 +         )
158 |     if cfg.token:
--------------------------------------------------------------------------------
173 |     if not getattr(server, "started", False):
    -         print(f"启动失败：端口 {cfg.port} 可能已被占用。"
    -               f"换一个端口试试：--port {cfg.port + 1}", file=sys.stderr)
174 +         print(
175 +             f"启动失败：端口 {cfg.port} 可能已被占用。换一个端口试试：--port {cfg.port + 1}",
176 +             file=sys.stderr,
177 +         )
178 |         return 3
    |

unformatted: File would be reformatted
   --> farm\server\actions.py:27:18
    |
26  |
    -     def __init__(self, name: str, kind: str, required: bool = True,
    -                  default: Any = None, desc: str = "") -> None:
27  +     def __init__(
28  +         self, name: str, kind: str, required: bool = True, default: Any = None, desc: str = ""
29  +     ) -> None:
30  |         self.name = name
    -         self.kind = kind            # "int" / "str" / "bool"
31  +         self.kind = kind  # "int" / "str" / "bool"
32  |         self.required = required
--------------------------------------------------------------------------------
76  |
    -     def __init__(self, name: str, summary: str, params: List[Param],
    -                  handler: Callable[[FarmEngine, Dict[str, Any]], Dict[str, Any]],
    -                  realtime: bool = True) -> None:
77  +     def __init__(
78  +         self,
79  +         name: str,
80  +         summary: str,
81  +         params: List[Param],
82  +         handler: Callable[[FarmEngine, Dict[str, Any]], Dict[str, Any]],
83  +         realtime: bool = True,
84  +     ) -> None:
85  |         self.name = name
--------------------------------------------------------------------------------
150 |
    - _register(Action("plant", "在指定地块播种", [
    -     Param("plot", "int", True, None, "地块编号，从 1 开始"),
    -     Param("crop", "str", True, None, "作物标识，如 wheat"),
    - ], lambda e, p: e.plant(p["plot"], p["crop"])))
151 + _register(
152 +     Action(
153 +         "plant",
154 +         "在指定地块播种",
155 +         [
156 +             Param("plot", "int", True, None, "地块编号，从 1 开始"),
157 +             Param("crop", "str", True, None, "作物标识，如 wheat"),
158 +         ],
159 +         lambda e, p: e.plant(p["plot"], p["crop"]),
160 +     )
161 + )
162 |
    - _register(Action("water", "给指定地块浇水（补满水分）", [
    -     Param("plot", "int", True, None, "地块编号，从 1 开始"),
    - ], lambda e, p: e.water(p["plot"])))
163 + _register(
164 +     Action(
165 +         "water",
166 +         "给指定地块浇水（补满水分）",
167 +         [
168 +             Param("plot", "int", True, None, "地块编号，从 1 开始"),
169 +         ],
170 +         lambda e, p: e.water(p["plot"]),
171 +     )
172 + )
173 |
    - _register(Action("harvest", "收获指定地块的成熟作物", [
    -     Param("plot", "int", True, None, "地块编号，从 1 开始"),
    - ], lambda e, p: e.harvest(p["plot"])))
174 + _register(
175 +     Action(
176 +         "harvest",
177 +         "收获指定地块的成熟作物",
178 +         [
179 +             Param("plot", "int", True, None, "地块编号，从 1 开始"),
180 +         ],
181 +         lambda e, p: e.harvest(p["plot"]),
182 +     )
183 + )
184 |
185 | _register(Action("harvest_all", "一键收获所有成熟作物", [], _harvest_all))
186 |
    - _register(Action("buy_animal", "购买牲畜", [
    -     Param("kind", "str", True, None, "牲畜标识：chicken / sheep / cow"),
    - ], lambda e, p: e.buy_animal(p["kind"])))
187 + _register(
188 +     Action(
189 +         "buy_animal",
190 +         "购买牲畜",
191 +         [
192 +             Param("kind", "str", True, None, "牲畜标识：chicken / sheep / cow"),
193 +         ],
194 +         lambda e, p: e.buy_animal(p["kind"]),
195 +     )
196 + )
197 |
    - _register(Action("feed", "喂食指定牲畜", [
    -     Param("animal_id", "int", True, None, "牲畜编号（state.animals[].id）"),
    -     Param("quantity", "int", False, None, "喂食份数，默认按需求；多喂无益"),
    - ], lambda e, p: e.feed(p["animal_id"], p.get("quantity"))))
198 + _register(
199 +     Action(
200 +         "feed",
201 +         "喂食指定牲畜",
202 +         [
203 +             Param("animal_id", "int", True, None, "牲畜编号（state.animals[].id）"),
204 +             Param("quantity", "int", False, None, "喂食份数，默认按需求；多喂无益"),
205 +         ],
206 +         lambda e, p: e.feed(p["animal_id"], p.get("quantity")),
207 +     )
208 + )
209 |
    - _register(Action("collect", "收取牲畜的产出并自动出售", [
    -     Param("animal_id", "int", True, None, "牲畜编号"),
    - ], lambda e, p: e.collect(p["animal_id"])))
210 + _register(
211 +     Action(
212 +         "collect",
213 +         "收取牲畜的产出并自动出售",
214 +         [
215 +             Param("animal_id", "int", True, None, "牲畜编号"),
216 +         ],
217 +         lambda e, p: e.collect(p["animal_id"]),
218 +     )
219 + )
220 |
    - _register(Action("buy_feed", "购买饲料", [
    -     Param("quantity", "int", False, config.FEED_BULK, f"份数，默认 {config.FEED_BULK}"),
    - ], lambda e, p: e.buy_feed(p.get("quantity", config.FEED_BULK))))
221 + _register(
222 +     Action(
223 +         "buy_feed",
224 +         "购买饲料",
225 +         [
226 +             Param("quantity", "int", False, config.FEED_BULK, f"份数，默认 {config.FEED_BULK}"),
227 +         ],
228 +         lambda e, p: e.buy_feed(p.get("quantity", config.FEED_BULK)),
229 +     )
230 + )
231 |
--------------------------------------------------------------------------------
235 |
    - _register(Action("pause", "暂停或恢复自动推进时间", [
    -     Param("paused", "bool", False, None, "true 暂停 / false 恢复；省略则反转当前状态"),
    - ], _pause))
236 + _register(
237 +     Action(
238 +         "pause",
239 +         "暂停或恢复自动推进时间",
240 +         [
241 +             Param("paused", "bool", False, None, "true 暂停 / false 恢复；省略则反转当前状态"),
242 +         ],
243 +         _pause,
244 +     )
245 + )
246 |
--------------------------------------------------------------------------------
259 |
    - def run_action(engine: FarmEngine, name: Any, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
260 + def run_action(
261 +     engine: FarmEngine, name: Any, params: Optional[Dict[str, Any]] = None
262 + ) -> Dict[str, Any]:
263 |     """校验参数并执行动作。
    |

unformatted: File would be reformatted
   --> farm\server\app.py:128:9
    |
127 |         f"2. 行动点：播种/浇水/喂食各消耗 1 点，收获与收取产出免费。行动点用完只能等第二天"
    -         "（POST /api/actions {\"action\":\"sleep\"} 可主动推进一天，有 2 秒冷却）。\n"
128 +         '（POST /api/actions {"action":"sleep"} 可主动推进一天，有 2 秒冷却）。\n'
129 |         f"3. 水分：作物每天消耗 1 点水分（上限 {config.WATER_CAPACITY}），水分为 0 时停止生长。"
--------------------------------------------------------------------------------
141 |         "  GET  /api/actions?farm=me   → 拿到当前所有**合法**动作（含开销与理由）\n"
    -         "  POST /api/actions           → {\"action\":\"<上一步里的 action>\","
    -         "\"params\":{...}} 直接执行\n"
142 +         '  POST /api/actions           → {"action":"<上一步里的 action>",'
143 +         '"params":{...}} 直接执行\n'
144 |         "  响应里的 state 就是执行后的最新状态，无需再查一次。\n"
145 |         "\n实时通道：ws://host/ws?farm=me ，服务器在状态变化时推送 state 消息；"
    -         "也可以直接在 WS 上发送 {\"type\":\"action\",\"action\":\"plant\","
    -         "\"params\":{\"plot\":1,\"crop\":\"wheat\"},\"id\":1} 并等待 result 回执。\n"
146 +         '也可以直接在 WS 上发送 {"type":"action","action":"plant",'
147 +         '"params":{"plot":1,"crop":"wheat"},"id":1} 并等待 result 回执。\n'
148 |     )
--------------------------------------------------------------------------------
252 |             session.engine.add_log(f"已存档到 {path}", "info")
    -             return {"ok": True, "action": "save", "path": path,
    -                     "message": f"已存档到 {path}"}
253 +             return {"ok": True, "action": "save", "path": path, "message": f"已存档到 {path}"}
254 |         if key == "reset":
255 |             session.reset()
    -             return {"ok": True, "action": "reset",
    -                     "message": f"牧场「{session.farm_id}」已重置，重新开始经营"}
256 +             return {
257 +                 "ok": True,
258 +                 "action": "reset",
259 +                 "message": f"牧场「{session.farm_id}」已重置，重新开始经营",
260 +             }
261 |         return actions_mod.run_action(session.engine, name, params)
262 |
    -     def run_and_wrap(session: FarmSession, name: Any, params: Any,
    -                      full: bool = False) -> Dict[str, Any]:
263 +     def run_and_wrap(
264 +         session: FarmSession, name: Any, params: Any, full: bool = False
265 +     ) -> Dict[str, Any]:
266 |         """执行动作并拼装统一响应。HTTP 与 WS 共用。"""
--------------------------------------------------------------------------------
318 |                 "log": "GET /api/log?farm=NAME&limit=20&since=0",
    -                 "execute": "POST /api/actions  {\"action\":\"plant\",\"params\":{\"plot\":1,\"crop\":\"wheat\"}}",
    -                 "farms": "GET /api/farms | POST /api/farms {\"farm\":\"NAME\"}",
319 +                 "execute": 'POST /api/actions  {"action":"plant","params":{"plot":1,"crop":"wheat"}}',
320 +                 "farms": 'GET /api/farms | POST /api/farms {"farm":"NAME"}',
321 |                 "realtime": "WS /ws?farm=NAME",
--------------------------------------------------------------------------------
409 |         limit = _int(request.query_params.get("limit"), 20, 1, 200)
    -         since = _int(request.query_params.get("since"), 0, 0, 10 ** 9)
410 +         since = _int(request.query_params.get("since"), 0, 0, 10**9)
411 |         entries = session.engine.recent_log(limit=limit, since=since)
--------------------------------------------------------------------------------
540 |             # 例如牧场数量达到上限：说清楚原因再关，别让客户端以为是网络问题
    -             await websocket.send_json({"type": "error", "error": exc.code,
    -                                        "message": exc.message})
541 +             await websocket.send_json({"type": "error", "error": exc.code, "message": exc.message})
542 |             await websocket.close(code=1013, reason=exc.message[:100])
--------------------------------------------------------------------------------
546 |
    -         await websocket.send_json({
    -             "type": "welcome",
    -             "client_id": client.cid,
    -             "farm": farm,
    -             "server": {
    -                 "name": SERVER_NAME,
    -                 "version": API_VERSION,
    -                 "world": cfg.world_name,
    -                 "tick_seconds": session.engine.tick_seconds,
    -             },
    -             "state": session.snapshot(),
    -             "catalog": session.engine.catalog(),
    -             "actions": session.engine.available_actions(),
    -             "log": session.engine.recent_log(limit=12),
    -             "online": hub.online(farm),
    -         })
547 +         await websocket.send_json(
548 +             {
549 +                 "type": "welcome",
550 +                 "client_id": client.cid,
551 +                 "farm": farm,
552 +                 "server": {
553 +                     "name": SERVER_NAME,
554 +                     "version": API_VERSION,
555 +                     "world": cfg.world_name,
556 +                     "tick_seconds": session.engine.tick_seconds,
557 +                 },
558 +                 "state": session.snapshot(),
559 +                 "catalog": session.engine.catalog(),
560 +                 "actions": session.engine.available_actions(),
561 +                 "log": session.engine.recent_log(limit=12),
562 +                 "online": hub.online(farm),
563 +             }
564 +         )
565 |         hub.broadcast(
566 |             farm,
    -             {"type": "notice", "farm": farm,
    -              "text": f"{client.name or client.cid} 来到了牧场（在线 {hub.online(farm)}）"},
567 +             {
568 +                 "type": "notice",
569 +                 "farm": farm,
570 +                 "text": f"{client.name or client.cid} 来到了牧场（在线 {hub.online(farm)}）",
571 +             },
572 |             exclude=client,
--------------------------------------------------------------------------------
594 |             except json.JSONDecodeError as exc:
    -                 await websocket.send_json({"type": "error", "error": "bad_json",
    -                                            "message": f"不是合法 JSON：{exc}"})
595 +                 await websocket.send_json(
596 +                     {"type": "error", "error": "bad_json", "message": f"不是合法 JSON：{exc}"}
597 +                 )
598 |                 return
599 |             if not isinstance(data, dict):
    -                 await websocket.send_json({"type": "error", "error": "bad_json",
    -                                            "message": "消息必须是 JSON 对象"})
600 +                 await websocket.send_json(
601 +                     {"type": "error", "error": "bad_json", "message": "消息必须是 JSON 对象"}
602 +                 )
603 |                 return
--------------------------------------------------------------------------------
612 |             if kind in ("sync", "refresh", "state"):
    -                 await websocket.send_json({
    -                     "type": "sync",
    -                     "id": rid,
    -                     "seq": next_seq(),
    -                     "farm": session.farm_id,
    -                     "state": session.snapshot(),
    -                     "actions": session.engine.available_actions(),
    -                     "log": session.engine.recent_log(limit=20),
    -                 })
613 +                 await websocket.send_json(
614 +                     {
615 +                         "type": "sync",
616 +                         "id": rid,
617 +                         "seq": next_seq(),
618 +                         "farm": session.farm_id,
619 +                         "state": session.snapshot(),
620 +                         "actions": session.engine.available_actions(),
621 +                         "log": session.engine.recent_log(limit=20),
622 +                     }
623 +                 )
624 |                 return
625 |
626 |             if kind == "help":
    -                 await websocket.send_json({"type": "help", "id": rid, "rules": rules_text(),
    -                                            "actions": actions_mod.describe_actions()})
627 +                 await websocket.send_json(
628 +                     {
629 +                         "type": "help",
630 +                         "id": rid,
631 +                         "rules": rules_text(),
632 +                         "actions": actions_mod.describe_actions(),
633 +                     }
634 +                 )
635 |                 return
--------------------------------------------------------------------------------
640 |                     result = await asyncio.to_thread(
    -                         execute_action, session, data.get("action"),
641 +                         execute_action,
642 +                         session,
643 +                         data.get("action"),
644 |                         data.get("params") or {},
645 |                     )
646 |                 except GameError as exc:
647 |                     counters["errors"] += 1
    -                     await websocket.send_json({"type": "result", "id": rid, "ok": False,
    -                                                "error": exc.code, "message": exc.message,
    -                                                "farm": session.farm_id})
648 +                     await websocket.send_json(
649 +                         {
650 +                             "type": "result",
651 +                             "id": rid,
652 +                             "ok": False,
653 +                             "error": exc.code,
654 +                             "message": exc.message,
655 +                             "farm": session.farm_id,
656 +                         }
657 +                     )
658 |                     return
659 |                 counters["actions"] += 1
660 |                 session.touch(count_action=True)
    -                 await websocket.send_json({
    -                     "type": "result",
    -                     "id": rid,
    -                     "ok": True,
    -                     "farm": session.farm_id,
    -                     "message": result.get("message", "完成"),
    -                     "result": result,
    -                     "state": brief(session, hub),
    -                     # 回执里带上完整快照：客户端可以立刻刷新画面，不必等下一次推送
    -                     "snapshot": session.snapshot(),
    -                     "log": session.engine.recent_log(limit=3),
    -                     "seq": next_seq(),
    -                 })
661 +                 await websocket.send_json(
662 +                     {
663 +                         "type": "result",
664 +                         "id": rid,
665 +                         "ok": True,
666 +                         "farm": session.farm_id,
667 +                         "message": result.get("message", "完成"),
668 +                         "result": result,
669 +                         "state": brief(session, hub),
670 +                         # 回执里带上完整快照：客户端可以立刻刷新画面，不必等下一次推送
671 +                         "snapshot": session.snapshot(),
672 +                         "log": session.engine.recent_log(limit=3),
673 +                         "seq": next_seq(),
674 +                     }
675 +                 )
676 |                 return
677 |
    -             await websocket.send_json({"type": "error", "error": "unknown_message",
    -                                        "message": f"不认识的消息类型 {kind!r}",
    -                                        "hint": "可用：action / ping / sync / help"})
678 +             await websocket.send_json(
679 +                 {
680 +                     "type": "error",
681 +                     "error": "unknown_message",
682 +                     "message": f"不认识的消息类型 {kind!r}",
683 +                     "hint": "可用：action / ping / sync / help",
684 +                 }
685 +             )
686 |
--------------------------------------------------------------------------------
701 |                 session.farm_id,
    -                 {"type": "notice", "farm": session.farm_id,
    -                  "text": f"{client.name or client.cid} 离开了牧场（在线 {hub.online(session.farm_id)}）"},
702 +                 {
703 +                     "type": "notice",
704 +                     "farm": session.farm_id,
705 +                     "text": f"{client.name or client.cid} 离开了牧场（在线 {hub.online(session.farm_id)}）",
706 +                 },
707 |             )
    |

unformatted: File would be reformatted
   --> farm\server\daemon.py:51:60
    |
50  |         kernel32 = ctypes.windll.kernel32
    -         handle = kernel32.OpenProcess(0x1000, False, pid)   # QUERY_LIMITED_INFORMATION
51  +         handle = kernel32.OpenProcess(0x1000, False, pid)  # QUERY_LIMITED_INFORMATION
52  |         if not handle:
--------------------------------------------------------------------------------
70  |
    -     def __init__(self, host: str = "127.0.0.1", port: int = 8765,
    -                  token: Optional[str] = None, tick: float = 15.0,
    -                  save_dir: str = "", pid_file: str = DEFAULT_PID_FILE,
    -                  log_file: str = DEFAULT_LOG_FILE, python: Optional[str] = None) -> None:
71  +     def __init__(
72  +         self,
73  +         host: str = "127.0.0.1",
74  +         port: int = 8765,
75  +         token: Optional[str] = None,
76  +         tick: float = 15.0,
77  +         save_dir: str = "",
78  +         pid_file: str = DEFAULT_PID_FILE,
79  +         log_file: str = DEFAULT_LOG_FILE,
80  +         python: Optional[str] = None,
81  +     ) -> None:
82  |         self.host = host
--------------------------------------------------------------------------------
150 |             sock.settimeout(0.6)
    -             return sock.connect_ex((self.host if self.host != "0.0.0.0" else "127.0.0.1",
    -                                     self.port)) == 0
151 +             return (
152 +                 sock.connect_ex((self.host if self.host != "0.0.0.0" else "127.0.0.1", self.port))
153 +                 == 0
154 +             )
155 |
156 |     # ------------------------------------------------------------ 启动
157 |     def command(self) -> List[str]:
158 |         args = [
    -             self.python, "-m", "farm.server",
    -             "--host", self.host,
    -             "--port", str(self.port),
    -             "--tick", str(self.tick),
    -             "--save-dir", self.save_dir,
159 +             self.python,
160 +             "-m",
161 +             "farm.server",
162 +             "--host",
163 +             self.host,
164 +             "--port",
165 +             str(self.port),
166 +             "--tick",
167 +             str(self.tick),
168 +             "--save-dir",
169 +             self.save_dir,
170 |             # 关键：pid 文件由**服务进程自己**写。
171 |             # Windows 上 venv 的 python.exe 可能是个转发器，父进程拿到的 pid
172 |             # 未必是真正在监听端口的那个进程，写错 pid 会导致 stop 杀错人。
    -             "--pid-file", self.pid_file,
173 +             "--pid-file",
174 +             self.pid_file,
175 |         ]
--------------------------------------------------------------------------------
190 |         os.makedirs(os.path.dirname(self.pid_file), exist_ok=True)
    -         self.clear_pid()            # 清掉上一次留下的陈旧 pid
191 +         self.clear_pid()  # 清掉上一次留下的陈旧 pid
192 |         env = dict(os.environ)
--------------------------------------------------------------------------------
245 |
    -         return False, (
    -             f"服务器进程已启动但 {wait:.0f} 秒内没响应。请查看日志：{self.log_file}"
    -         )
246 +         return False, (f"服务器进程已启动但 {wait:.0f} 秒内没响应。请查看日志：{self.log_file}")
247 |
--------------------------------------------------------------------------------
277 |         if force and os.name == "nt":
    -             subprocess.run(["taskkill", "/F", "/T", "/PID", str(pid)],
    -                            capture_output=True, check=False)
278 +             subprocess.run(
279 +                 ["taskkill", "/F", "/T", "/PID", str(pid)], capture_output=True, check=False
280 +             )
281 |             if self._wait_offline(pid, 3.0):
--------------------------------------------------------------------------------
308 |             url += "?token=" + urllib.parse.quote(self.token)
    -         request = urllib.request.Request(url, data=b"{}", method="POST",
    -                                         headers={"Content-Type": "application/json"})
309 +         request = urllib.request.Request(
310 +             url, data=b"{}", method="POST", headers={"Content-Type": "application/json"}
311 +         )
312 |         try:
    |

unformatted: File would be reformatted
   --> farm\server\hub.py:31:24
    |
30  |
    - STATE_INTERVAL = 0.25      # 状态推送的最小间隔（秒），天然把高频操作合并成一次
    - QUEUE_SIZE = 32            # 单连接出口缓冲区；满了就丢最旧的（状态类消息可丢）
31  + STATE_INTERVAL = 0.25  # 状态推送的最小间隔（秒），天然把高频操作合并成一次
32  + QUEUE_SIZE = 32  # 单连接出口缓冲区；满了就丢最旧的（状态类消息可丢）
33  |
--------------------------------------------------------------------------------
50  |
    -     __slots__ = ("cid", "farm_id", "loop", "queue", "name", "joined_at", "alive",
    -                  "_sent", "_dropped")
51  +     __slots__ = (
52  +         "cid",
53  +         "farm_id",
54  +         "loop",
55  +         "queue",
56  +         "name",
57  +         "joined_at",
58  +         "alive",
59  +         "_sent",
60  +         "_dropped",
61  +     )
62  |
--------------------------------------------------------------------------------
156 |     # ------------------------------------------------------------ 广播
    -     def broadcast(self, farm_id: str, message: Dict[str, Any],
    -                   exclude: Optional[Client] = None) -> int:
157 +     def broadcast(
158 +         self, farm_id: str, message: Dict[str, Any], exclude: Optional[Client] = None
159 +     ) -> int:
160 |         sent = 0
--------------------------------------------------------------------------------
231 |             session.published_day = day
    -             self.hub.broadcast(session.farm_id, {
    -                 "type": "day",
    -                 "farm": session.farm_id,
    -                 "day": day,
    -                 "events": session.last_events,
    -                 "at": now,
    -             })
232 +             self.hub.broadcast(
233 +                 session.farm_id,
234 +                 {
235 +                     "type": "day",
236 +                     "farm": session.farm_id,
237 +                     "day": day,
238 +                     "events": session.last_events,
239 +                     "at": now,
240 +                 },
241 +             )
242 |         self.hub.broadcast(session.farm_id, payload)
--------------------------------------------------------------------------------
248 |
    - def build_state_message(session, seq: int, note: str = "", extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
249 + def build_state_message(
250 +     session, seq: int, note: str = "", extra: Optional[Dict[str, Any]] = None
251 + ) -> Dict[str, Any]:
252 |     """构造给客户端的状态消息（HTTP 与 WS 共用同一份结构）。"""
    |

unformatted: File would be reformatted
   --> farm\server\session.py:66:73
    |
65  |
    -     def __init__(self, farm_id: str, save_dir: str, tick_seconds: float,
    -                  owner: str = "") -> None:
66  +     def __init__(self, farm_id: str, save_dir: str, tick_seconds: float, owner: str = "") -> None:
67  |         self.farm_id = farm_id
--------------------------------------------------------------------------------
182 |
    -     def __init__(self, save_dir: str, tick_seconds: float = config.DEFAULT_TICK_SECONDS,
    -                  max_sessions: int = 64, idle_timeout: float = 1800.0) -> None:
183 +     def __init__(
184 +         self,
185 +         save_dir: str,
186 +         tick_seconds: float = config.DEFAULT_TICK_SECONDS,
187 +         max_sessions: int = 64,
188 +         idle_timeout: float = 1800.0,
189 +     ) -> None:
190 |         self.save_dir = save_dir
--------------------------------------------------------------------------------
209 |                     "too_many_farms",
    -                     f"服务器牧场数量已达上限（{self.max_sessions}），"
    -                     "请稍后再试或联系管理员扩容",
210 +                     f"服务器牧场数量已达上限（{self.max_sessions}），请稍后再试或联系管理员扩容",
211 |                     status=503,
--------------------------------------------------------------------------------
258 |             if session.engine.paused:
    -                 continue          # 暂停是人为操作，说明玩家还会回来
259 +                 continue  # 暂停是人为操作，说明玩家还会回来
260 |             if now - session.last_active < self.idle_timeout:
--------------------------------------------------------------------------------
264 |             except OSError:
    -                 continue          # 存不下就别丢，宁可占着内存
265 +                 continue  # 存不下就别丢，宁可占着内存
266 |             with self._lock:
    |

unformatted: File would be reformatted
   --> farm\server\webui.py:434:9
    |
433 |         "     -H 'Content-Type: application/json' \\\n"
    -         "     -d '{\"action\":\"plant\",\"params\":{\"plot\":1,\"crop\":\"wheat\"}}'\n"
434 +         '     -d \'{"action":"plant","params":{"plot":1,"crop":"wheat"}}\'\n'
435 |         "\n"
--------------------------------------------------------------------------------
447 |         "------\n"
    -         "POST /api/actions      {\"action\":\"...\",\"params\":{...}}  通用入口\n"
    -         "POST /api/batch        {\"actions\":[{...},{...}]}           批量\n"
448 +         'POST /api/actions      {"action":"...","params":{...}}  通用入口\n'
449 +         'POST /api/batch        {"actions":[{...},{...}]}           批量\n'
450 |         "POST /api/plant|water|harvest|harvest_all|buy_animal|feed|\n"
451 |         "     collect|buy_feed|buy_plot|sleep|pause|save|reset       便捷入口\n"
    -         "POST /api/farms        {\"farm\":\"名字\"} 创建/进入牧场\n"
452 +         'POST /api/farms        {"farm":"名字"} 创建/进入牧场\n'
453 |         "POST /api/admin/save   全部存档\n"
    |

unformatted: File would be reformatted
   --> farm\tui.py:44:18
    |
43  | ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
    - CELL_WIDTH = 17      # 单块农田格子的显示宽度
44  + CELL_WIDTH = 17  # 单块农田格子的显示宽度
45  | CELL_INNER = CELL_WIDTH - 2
    - SIDE_WIDTH = 36      # 右侧栏宽度
46  + SIDE_WIDTH = 36  # 右侧栏宽度
47  | MIN_TERM_WIDTH = 76
    - MAX_VISIBLE_ANIMALS = 5   # 侧栏同时最多展示的牲畜数量
48  + MAX_VISIBLE_ANIMALS = 5  # 侧栏同时最多展示的牲畜数量
49  |
--------------------------------------------------------------------------------
135 | # ---------------------------------------------------------------- 绘制工具
    - def draw_box(title: str, body: List[str], width: int, accent: Optional[str] = None,
    -              use_color: bool = True) -> List[str]:
136 + def draw_box(
137 +     title: str, body: List[str], width: int, accent: Optional[str] = None, use_color: bool = True
138 + ) -> List[str]:
139 |     """画一个带标题的方框，返回行列表（宽度精确等于 ``width``）。"""
--------------------------------------------------------------------------------
168 |
    -     def __init__(self, engine: Any, save_path: Optional[str] = None,
    -                  use_color: bool = True, autosave: bool = True,
    -                  server_label: str = "") -> None:
169 +     def __init__(
170 +         self,
171 +         engine: Any,
172 +         save_path: Optional[str] = None,
173 +         use_color: bool = True,
174 +         autosave: bool = True,
175 +         server_label: str = "",
176 +     ) -> None:
177 |         # engine 既可以是本地 FarmEngine，也可以是远程 RemoteFarm——只要接口一致
--------------------------------------------------------------------------------
185 |         self.running = True
    -         self.mode = "main"          # main / help / select_crop / select_animal / confirm_reset
186 +         self.mode = "main"  # main / help / select_crop / select_animal / confirm_reset
187 |         self.plot_cursor = 0
--------------------------------------------------------------------------------
211 |             try:
    -                 self.engine.save(self.save_path)     # 本地引擎写文件；远程引擎让服务器落盘
212 +                 self.engine.save(self.save_path)  # 本地引擎写文件；远程引擎让服务器落盘
213 |             except Exception:
--------------------------------------------------------------------------------
620 |             filled_count = max(0, min(10, int(round(ratio * 10))))
    -             progress = color("█" * filled_count + "░" * (10 - filled_count),
    -                              C_GREEN if plot["is_ripe"] else C_CYAN, self.use_color)
621 +             progress = color(
622 +                 "█" * filled_count + "░" * (10 - filled_count),
623 +                 C_GREEN if plot["is_ripe"] else C_CYAN,
624 +                 self.use_color,
625 +             )
626 |
--------------------------------------------------------------------------------
640 |         body = [
    -             color("│", border, self.use_color) + pad(line1, CELL_INNER) + color("│", border, self.use_color),
    -             color("│", border, self.use_color) + pad(line2, CELL_INNER) + color("│", border, self.use_color),
641 +             color("│", border, self.use_color)
642 +             + pad(line1, CELL_INNER)
643 +             + color("│", border, self.use_color),
644 +             color("│", border, self.use_color)
645 +             + pad(line2, CELL_INNER)
646 +             + color("│", border, self.use_color),
647 |         ]
--------------------------------------------------------------------------------
658 |             # 只渲染一个跟随光标的窗口，避免牲畜变多后把界面撑爆
    -             start = max(0, min(max(0, len(animals) - MAX_VISIBLE_ANIMALS),
    -                                self.animal_cursor - MAX_VISIBLE_ANIMALS // 2))
659 +             start = max(
660 +                 0,
661 +                 min(
662 +                     max(0, len(animals) - MAX_VISIBLE_ANIMALS),
663 +                     self.animal_cursor - MAX_VISIBLE_ANIMALS // 2,
664 +                 ),
665 +             )
666 |             for index in range(start, min(len(animals), start + MAX_VISIBLE_ANIMALS)):
--------------------------------------------------------------------------------
671 |                 if animal["pending"] > 0:
    -                     status = color(f"{animal['product']}x{animal['pending']}",
    -                                    C_BOLD + C_GOLD, self.use_color)
672 +                     status = color(
673 +                         f"{animal['product']}x{animal['pending']}", C_BOLD + C_GOLD, self.use_color
674 +                     )
675 |                 elif animal["is_sick"]:
--------------------------------------------------------------------------------
683 |
    -                 head = marker + color(f" #{animal['id']} {animal['name']}",
    -                                       C_WHITE, self.use_color)
684 +                 head = marker + color(f" #{animal['id']} {animal['name']}", C_WHITE, self.use_color)
685 |                 rows.append(pad(head, 13) + status)
--------------------------------------------------------------------------------
689 |                 rows.append(color(f"  …还有 {hidden} 只（Tab 切换）", C_DIM, self.use_color))
    -         pasture = draw_box(f"牧场 {snap['animal_count']}/{snap['max_animals']}", rows,
    -                            side_width, accent=C_ORANGE, use_color=self.use_color)
690 +         pasture = draw_box(
691 +             f"牧场 {snap['animal_count']}/{snap['max_animals']}",
692 +             rows,
693 +             side_width,
694 +             accent=C_ORANGE,
695 +             use_color=self.use_color,
696 +         )
697 |
--------------------------------------------------------------------------------
719 |         )
    -         market = draw_box("市场行情", market_rows, side_width,
    -                           accent=C_CYAN, use_color=self.use_color)
720 +         market = draw_box(
721 +             "市场行情", market_rows, side_width, accent=C_CYAN, use_color=self.use_color
722 +         )
723 |
--------------------------------------------------------------------------------
780 |         rows = [
    -             color(" 玩法目标：靠种地和养殖把净资产做到 10000 金币，解锁全部 5 个里程碑。", C_WHITE, self.use_color),
781 +             color(
782 +                 " 玩法目标：靠种地和养殖把净资产做到 10000 金币，解锁全部 5 个里程碑。",
783 +                 C_WHITE,
784 +                 self.use_color,
785 +             ),
786 |             "",
787 |             color(" 时间", C_BOLD + C_GOLD, self.use_color)
    -             + color("   每 15 秒自动推进一天，作物成长、牲畜产奶、市场价格都在这一天结算。", C_GRAY, self.use_color),
788 +             + color(
789 +                 "   每 15 秒自动推进一天，作物成长、牲畜产奶、市场价格都在这一天结算。",
790 +                 C_GRAY,
791 +                 self.use_color,
792 +             ),
793 |             color(" 水分", C_BOLD + C_GOLD, self.use_color)
    -             + color("   作物每天消耗 1 点水分，水为 0 就停止生长。按 W 浇水恢复到 3 点。", C_GRAY, self.use_color),
794 +             + color(
795 +                 "   作物每天消耗 1 点水分，水为 0 就停止生长。按 W 浇水恢复到 3 点。",
796 +                 C_GRAY,
797 +                 self.use_color,
798 +             ),
799 |             color(" 行动点", C_BOLD + C_GOLD, self.use_color)
    -             + color("  每天 10 点，播种/浇水/喂食各消耗 1 点，收获和收取免费。", C_GRAY, self.use_color),
800 +             + color(
801 +                 "  每天 10 点，播种/浇水/喂食各消耗 1 点，收获和收取免费。", C_GRAY, self.use_color
802 +             ),
803 |             color(" 牲畜", C_BOLD + C_GOLD, self.use_color)
    -             + color("   必须每天喂食才会产出；连续 3 天没吃东西会离开农场。", C_GRAY, self.use_color),
804 +             + color(
805 +                 "   必须每天喂食才会产出；连续 3 天没吃东西会离开农场。", C_GRAY, self.use_color
806 +             ),
807 |             color(" 市场", C_BOLD + C_GOLD, self.use_color)
808 |             + color("   作物和畜产品价格每天波动 ±18%，高点卖出更赚。", C_GRAY, self.use_color),
809 |             "",
810 |             color(" 快捷键", C_BOLD + C_GOLD, self.use_color)
    -             + color("  P 播种 / W 浇水 / H 收获 / E 一键收获 / F 喂食 / C 收取产出", C_GRAY, self.use_color),
811 +             + color(
812 +                 "  P 播种 / W 浇水 / H 收获 / E 一键收获 / F 喂食 / C 收取产出",
813 +                 C_GRAY,
814 +                 self.use_color,
815 +             ),
816 |             color("          ", C_GRAY, self.use_color)
    -             + color(" B 买动物 / L 开垦土地 / K 买饲料 / N 休息 / 空格 暂停 / S 存档 / R 重置", C_GRAY, self.use_color),
817 +             + color(
818 +                 " B 买动物 / L 开垦土地 / K 买饲料 / N 休息 / 空格 暂停 / S 存档 / R 重置",
819 +                 C_GRAY,
820 +                 self.use_color,
821 +             ),
822 |             "",
823 |             color(" AI 接入", C_BOLD + C_GOLD, self.use_color)
    -             + color("   游戏跑在服务器进程里，HTTP 接口与 WebSocket 通道同时开放，",
    -                     C_GRAY, self.use_color),
824 +             + color(
825 +                 "   游戏跑在服务器进程里，HTTP 接口与 WebSocket 通道同时开放，",
826 +                 C_GRAY,
827 +                 self.use_color,
828 +             ),
829 |             color("          ", C_GRAY, self.use_color)
    -             + color(" AI 与你在同一个农场里操作（见 README「给 AI 的接口」）。",
    -                     C_GRAY, self.use_color),
830 +             + color(
831 +                 " AI 与你在同一个农场里操作（见 README「给 AI 的接口」）。", C_GRAY, self.use_color
832 +             ),
833 |             "",
    |

unformatted: File would be reformatted
   --> main.py:101:26
    |
100 |     p_serve.add_argument("--daemon", action="store_true", help="转到后台运行（守护进程）")
    -     p_serve.add_argument("--host", default="127.0.0.1",
    -                          help="监听地址，默认 127.0.0.1；局域网联机用 0.0.0.0")
101 +     p_serve.add_argument(
102 +         "--host", default="127.0.0.1", help="监听地址，默认 127.0.0.1；局域网联机用 0.0.0.0"
103 +     )
104 |     p_serve.add_argument("--port", type=int, default=8765, help="监听端口，默认 8765")
105 |     p_serve.add_argument("--token", default=None, help="访问令牌，设置后接口需要携带")
    -     p_serve.add_argument("--tick", type=float, default=config.DEFAULT_TICK_SECONDS,
    -                          help=f"每多少秒推进一天，默认 {config.DEFAULT_TICK_SECONDS:.0f} 秒")
106 +     p_serve.add_argument(
107 +         "--tick",
108 +         type=float,
109 +         default=config.DEFAULT_TICK_SECONDS,
110 +         help=f"每多少秒推进一天，默认 {config.DEFAULT_TICK_SECONDS:.0f} 秒",
111 +     )
112 |     p_serve.add_argument("--save-dir", default=None, help="存档目录，默认 <项目>/saves")
113 |     p_serve.add_argument("--world", default="SimpleFarm 公共农场", help="世界名称")
114 |     p_serve.add_argument("--max-sessions", type=int, default=64, help="同时在线的牧场上限")
    -     p_serve.add_argument("--idle-timeout", type=float, default=1800.0,
    -                          help="无人访问多少秒后把牧场移出内存（存档保留）")
115 +     p_serve.add_argument(
116 +         "--idle-timeout",
117 +         type=float,
118 +         default=1800.0,
119 +         help="无人访问多少秒后把牧场移出内存（存档保留）",
120 +     )
121 |     p_serve.add_argument("--no-remote-control", action="store_true", help="禁止接口停机")
122 |     p_serve.add_argument("--access-log", action="store_true", help="打印每个 HTTP 请求")
    -     p_serve.add_argument("--log-level", default="info",
    -                          choices=["critical", "error", "warning", "info", "debug"])
123 +     p_serve.add_argument(
124 +         "--log-level", default="info", choices=["critical", "error", "warning", "info", "debug"]
125 +     )
126 |     p_serve.add_argument("--log-file", default=None, help="守护进程日志路径")
--------------------------------------------------------------------------------
151 |     p_play.add_argument("--no-color", action="store_true")
    -     p_play.add_argument("--no-realtime", action="store_true",
    -                         help="不用 WebSocket，改走 HTTP 轮询")
152 +     p_play.add_argument("--no-realtime", action="store_true", help="不用 WebSocket，改走 HTTP 轮询")
153 |
--------------------------------------------------------------------------------
161 |     p_do.add_argument("action", help="动作名：plant / water / harvest / ...")
    -     p_do.add_argument("params", nargs="?", default="{}",
    -                       help='JSON 参数，例如 \'{"plot":1,"crop":"wheat"}\'')
162 +     p_do.add_argument(
163 +         "params", nargs="?", default="{}", help='JSON 参数，例如 \'{"plot":1,"crop":"wheat"}\''
164 +     )
165 |     _add_client_args(p_do)
--------------------------------------------------------------------------------
214 |         return RemoteFarm(
    -             endpoint=args.url, farm=getattr(args, "farm", "default"),
215 +             endpoint=args.url,
216 +             farm=getattr(args, "farm", "default"),
217 |             token=getattr(args, "token", None),
218 |             name=getattr(args, "name", "") or "cli",
    -             realtime=realtime, autostart=True,
219 +             realtime=realtime,
220 +             autostart=True,
221 |         )
--------------------------------------------------------------------------------
235 |     argv = [
    -         "--host", args.host, "--port", str(args.port),
    -         "--tick", str(args.tick), "--world", args.world,
    -         "--max-sessions", str(args.max_sessions),
    -         "--idle-timeout", str(args.idle_timeout),
    -         "--log-level", args.log_level,
236 +         "--host",
237 +         args.host,
238 +         "--port",
239 +         str(args.port),
240 +         "--tick",
241 +         str(args.tick),
242 +         "--world",
243 +         args.world,
244 +         "--max-sessions",
245 +         str(args.max_sessions),
246 +         "--idle-timeout",
247 +         str(args.idle_timeout),
248 +         "--log-level",
249 +         args.log_level,
250 |     ]
--------------------------------------------------------------------------------
307 |         for row in world["leaderboard"][:5]:
    -             print(f"    #{row['rank']} {row['farm']:<14} 净资产 {row['net_worth']:>8.0f}"
    -                   f" · 第 {row['day']} 天 · Lv.{row['level']} · 在线 {row['online']}")
308 +             print(
309 +                 f"    #{row['rank']} {row['farm']:<14} 净资产 {row['net_worth']:>8.0f}"
310 +                 f" · 第 {row['day']} 天 · Lv.{row['level']} · 在线 {row['online']}"
311 +             )
312 |
--------------------------------------------------------------------------------
342 |     label = f"{client.farm} @ {client.host}"
    -     app = TuiApp(client, save_path=None, use_color=not args.no_color,
    -                  autosave=True, server_label=label)
343 +     app = TuiApp(
344 +         client, save_path=None, use_color=not args.no_color, autosave=True, server_label=label
345 +     )
346 |     try:
--------------------------------------------------------------------------------
392 |     except json.JSONDecodeError as exc:
    -         return die(f"参数不是合法 JSON：{exc}", '例子：python main.py do plant \'{"plot":1,"crop":"wheat"}\'')
393 +         return die(
394 +             f"参数不是合法 JSON：{exc}",
395 +             '例子：python main.py do plant \'{"plot":1,"crop":"wheat"}\'',
396 +         )
397 |     if not isinstance(params, dict):
--------------------------------------------------------------------------------
403 |     except GameError as exc:
    -         return die(f"{args.action} 失败（{exc.code}）：{exc.message}",
    -                    "看看现在能做什么：python main.py actions")
404 +         return die(
405 +             f"{args.action} 失败（{exc.code}）：{exc.message}",
406 +             "看看现在能做什么：python main.py actions",
407 +         )
408 |
    -     print(ok("✓ ") + str(result.get("message", "完成"))
    -           + dim(f"  ({time.time() - started:.2f}s)"))
    -     extra = {k: v for k, v in result.items()
    -              if k not in ("ok", "message", "state", "log", "action") and not isinstance(v, (dict, list))}
409 +     print(ok("✓ ") + str(result.get("message", "完成")) + dim(f"  ({time.time() - started:.2f}s)"))
410 +     extra = {
411 +         k: v
412 +         for k, v in result.items()
413 +         if k not in ("ok", "message", "state", "log", "action") and not isinstance(v, (dict, list))
414 +     }
415 |     if extra:
416 |         print(dim("  " + "  ".join(f"{k}={v}" for k, v in extra.items())))
417 |     state = result.get("state") or {}
418 |     if state:
    -         print(f"  第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
    -               f"行动点 {state.get('ap')}/{state.get('max_ap')} · 净资产 {state.get('net_worth')}")
419 +         print(
420 +             f"  第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
421 +             f"行动点 {state.get('ap')}/{state.get('max_ap')} · 净资产 {state.get('net_worth')}"
422 +         )
423 |     return 0
--------------------------------------------------------------------------------
432 |     except json.JSONDecodeError as exc:
    -         return die(f"actions 不是合法 JSON：{exc}",
    -                    '例子：python main.py batch \'[{"action":"harvest_all"},{"action":"buy_feed","params":{"quantity":5}}]\'')
433 +         return die(
434 +             f"actions 不是合法 JSON：{exc}",
435 +             '例子：python main.py batch \'[{"action":"harvest_all"},{"action":"buy_feed","params":{"quantity":5}}]\'',
436 +         )
437 |     if not isinstance(items, list):
--------------------------------------------------------------------------------
453 |     items = client.available_actions()
    -     print(f"牧场 {client.farm} · 第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
    -           f"行动点 {state.get('ap')}/{state.get('max_ap')}")
454 +     print(
455 +         f"牧场 {client.farm} · 第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
456 +         f"行动点 {state.get('ap')}/{state.get('max_ap')}"
457 +     )
458 |     if not items:
--------------------------------------------------------------------------------
465 |         cost_text = " ".join(f"{k}:{v}" for k, v in cost.items()) or "免费"
    -         print(f"  [{index:>2}] {item['action']:<12} {json.dumps(item['params'], ensure_ascii=False):<42}"
    -               f" ({cost_text})")
466 +         print(
467 +             f"  [{index:>2}] {item['action']:<12} {json.dumps(item['params'], ensure_ascii=False):<42}"
468 +             f" ({cost_text})"
469 +         )
470 |         print(dim(f"       {item.get('reason', '')}"))
--------------------------------------------------------------------------------
481 |     print(f"牧场      {snap.get('farm')}")
    -     print(f"第        {snap.get('day')} 天"
    -           + (f" · {snap.get('seconds_to_tick')}s 后进入下一天"
    -              if snap.get("seconds_to_tick") is not None else " · 已暂停"))
    -     print(f"金币      {snap.get('gold')}   等级 Lv.{snap.get('level')} "
    -           f"({snap.get('exp')}/{snap.get('exp_needed')})")
482 +     print(
483 +         f"第        {snap.get('day')} 天"
484 +         + (
485 +             f" · {snap.get('seconds_to_tick')}s 后进入下一天"
486 +             if snap.get("seconds_to_tick") is not None
487 +             else " · 已暂停"
488 +         )
489 +     )
490 +     print(
491 +         f"金币      {snap.get('gold')}   等级 Lv.{snap.get('level')} "
492 +         f"({snap.get('exp')}/{snap.get('exp_needed')})"
493 +     )
494 |     print(f"行动点    {snap.get('ap')}/{snap.get('max_ap')}   饲料 {snap.get('feed')}")
--------------------------------------------------------------------------------
500 |         status = "可收获" if plot["is_ripe"] else f"还需{plot['days_left']}天"
    -         print(f"  {plot['position']:>2} 号地 {plot['crop_name']:<6} 生长 {plot['growth']}"
    -               f"/{plot['grow_days']} 水分 {plot['moisture']} · {status}")
501 +         print(
502 +             f"  {plot['position']:>2} 号地 {plot['crop_name']:<6} 生长 {plot['growth']}"
503 +             f"/{plot['grow_days']} 水分 {plot['moisture']} · {status}"
504 +         )
505 |     for animal in snap.get("animals", []):
    -         note = (f"{animal['product']} x{animal['pending']} 待收" if animal["pending"]
    -                 else ("已喂食" if animal["fed"] else "未喂食"))
506 +         note = (
507 +             f"{animal['product']} x{animal['pending']} 待收"
508 +             if animal["pending"]
509 +             else ("已喂食" if animal["fed"] else "未喂食")
510 +         )
511 |         print(f"  #{animal['id']} {animal['name']:<4} {note}")
--------------------------------------------------------------------------------
537 |             online = snap.get("online")
    -             head = (f"第 {snap.get('day')} 天 | 金币 {snap.get('gold')} | "
    -                     f"Lv.{snap.get('level')} | 行动点 {snap.get('ap')}/{snap.get('max_ap')} | "
    -                     f"净资产 {snap.get('net_worth')}")
538 +             head = (
539 +                 f"第 {snap.get('day')} 天 | 金币 {snap.get('gold')} | "
540 +                 f"Lv.{snap.get('level')} | 行动点 {snap.get('ap')}/{snap.get('max_ap')} | "
541 +                 f"净资产 {snap.get('net_worth')}"
542 +             )
543 |             if online is not None:
--------------------------------------------------------------------------------
572 |     world = client.world()
    -     print(f"世界：{world.get('world')} · 已运行 {world.get('uptime', 0):.0f} 秒 · "
    -           f"牧场 {world.get('farms')} 个 · 连接 {world.get('clients')} 个")
573 +     print(
574 +         f"世界：{world.get('world')} · 已运行 {world.get('uptime', 0):.0f} 秒 · "
575 +         f"牧场 {world.get('farms')} 个 · 连接 {world.get('clients')} 个"
576 +     )
577 |     print(f"时钟：每 {world.get('tick_seconds')} 秒推进一天\n")
578 |     print(f"  {'#':>2} {'牧场':<14} {'净资产':>9} {'金币':>7} {'天':>4} {'等级':>4} {'在线':>4}")
579 |     for row in world.get("leaderboard", []):
    -         print(f"  {row['rank']:>2} {row['farm']:<14} {row['net_worth']:>9.0f} "
    -               f"{row['gold']:>7} {row['day']:>4} {row['level']:>4} {row['online']:>4}")
580 +         print(
581 +             f"  {row['rank']:>2} {row['farm']:<14} {row['net_worth']:>9.0f} "
582 +             f"{row['gold']:>7} {row['day']:>4} {row['level']:>4} {row['online']:>4}"
583 +         )
584 |     return 0
--------------------------------------------------------------------------------
660 |                 health = info.get("health") or {}
    -                 print(ok("✓ 服务器正在运行")
    -                       + f"  http://{info['host']}:{info['port']}/  "
    -                       + dim(f"牧场 {health.get('farms', 0)} 个 · 在线 {health.get('clients', 0)}"))
661 +                 print(
662 +                     ok("✓ 服务器正在运行")
663 +                     + f"  http://{info['host']}:{info['port']}/  "
664 +                     + dim(f"牧场 {health.get('farms', 0)} 个 · 在线 {health.get('clients', 0)}")
665 +                 )
666 |             else:
--------------------------------------------------------------------------------
677 |         return handler(args)
    -     except SystemExit as exc:                      # make_client 里的友好退出
678 +     except SystemExit as exc:  # make_client 里的友好退出
679 |         return int(exc.code or 0)
    |

unformatted: File would be reformatted
   --> tests\test_engine.py:19:26
    |
18  |
    - from farm import config                      # noqa: E402
19  + from farm import config  # noqa: E402
20  | from farm.engine import FarmEngine, GameError  # noqa: E402
    - from farm.models import GameState, Plot      # noqa: E402
21  + from farm.models import GameState, Plot  # noqa: E402
22  | from farm.storage import load_state, save_state  # noqa: E402
--------------------------------------------------------------------------------
192 |     def test_buy_plot_without_gold(self) -> None:
    -         self.engine.state.level = 5        # 先解锁土地上限，才能走到"钱不够"这一步
193 +         self.engine.state.level = 5  # 先解锁土地上限，才能走到"钱不够"这一步
194 |         self.engine.state.gold = 0
--------------------------------------------------------------------------------
199 |     def test_buy_plot_blocked_at_level_cap(self) -> None:
    -         self.engine.state.gold = 10 ** 6
200 +         self.engine.state.gold = 10**6
201 |         with self.assertRaises(GameError) as ctx:
--------------------------------------------------------------------------------
264 |     def test_pasture_limit(self) -> None:
    -         self.engine.state.gold = 10 ** 6
265 +         self.engine.state.gold = 10**6
266 |         for _ in range(config.MAX_ANIMALS):
--------------------------------------------------------------------------------
282 |     def test_buy_feed_validation(self) -> None:
    -         self.engine.state.gold = 10 ** 6
283 +         self.engine.state.gold = 10**6
284 |         with self.assertRaises(GameError):
--------------------------------------------------------------------------------
352 |         self.engine.buy_animal("chicken")
    -         self.engine.state.gold = 500        # 操作完成后再定金币，避免被扣费干扰断言
353 +         self.engine.state.gold = 500  # 操作完成后再定金币，避免被扣费干扰断言
354 |         self.engine.state.feed = 7
--------------------------------------------------------------------------------
404 |         self.assertEqual(state.level, 1)
    -         self.assertIsNone(state.plots[0].crop)          # 非法作物被清空
    -         self.assertEqual(state.animals, [])              # 非法动物被丢弃
    -         self.assertGreater(state.market["wheat"], 0)     # 非法价格回落默认值
405 +         self.assertIsNone(state.plots[0].crop)  # 非法作物被清空
406 +         self.assertEqual(state.animals, [])  # 非法动物被丢弃
407 +         self.assertGreater(state.market["wheat"], 0)  # 非法价格回落默认值
408 |         self.assertGreaterEqual(len(state.plots), config.BASE_PLOTS)
409 |
410 |     # ============================================================ 并发
411 |     def test_concurrent_operations_are_safe(self) -> None:
    -         self.engine.state.gold = 10 ** 6
412 +         self.engine.state.gold = 10**6
413 |         errors = []
    |

unformatted: File would be reformatted
   --> tests\test_server.py:35:22
    |
34  |     HAS_FASTAPI = True
    - except ImportError:      # pragma: no cover - 取决于环境
35  + except ImportError:  # pragma: no cover - 取决于环境
36  |     HAS_FASTAPI = False
--------------------------------------------------------------------------------
41  |     HAS_UVICORN = True
    - except ImportError:      # pragma: no cover
42  + except ImportError:  # pragma: no cover
43  |     HAS_UVICORN = False
--------------------------------------------------------------------------------
233 |         self.tmp = tempfile.mkdtemp(prefix="farm-api-")
    -         self.cfg = ServerConfig(host="127.0.0.1", port=8765, save_dir=self.tmp,
    -                                 tick_seconds=60)
234 +         self.cfg = ServerConfig(host="127.0.0.1", port=8765, save_dir=self.tmp, tick_seconds=60)
235 |         self.app = create_app(self.cfg)
--------------------------------------------------------------------------------
278 |         self.assertIn("已种下", data["message"])
    -         self.assertEqual(data["state"]["gold"], config.START_GOLD - config.CROPS["wheat"]["seed_cost"])
279 +         self.assertEqual(
280 +             data["state"]["gold"], config.START_GOLD - config.CROPS["wheat"]["seed_cost"]
281 +         )
282 |
--------------------------------------------------------------------------------
287 |         """{"action":"plant","plot":1,"crop":"wheat"} 这种平铺写法也要能用。"""
    -         data = self.client.post("/api/actions?farm=flat",
    -                                 json={"action": "plant", "plot": 1, "crop": "wheat"}).json()
288 +         data = self.client.post(
289 +             "/api/actions?farm=flat", json={"action": "plant", "plot": 1, "crop": "wheat"}
290 +         ).json()
291 |         self.assertTrue(data["ok"])
--------------------------------------------------------------------------------
298 |     def test_action_error_shape(self) -> None:
    -         response = self.client.post("/api/actions?farm=err",
    -                                     json={"action": "plant", "params": {"plot": 1, "crop": "nope"}})
299 +         response = self.client.post(
300 +             "/api/actions?farm=err", json={"action": "plant", "params": {"plot": 1, "crop": "nope"}}
301 +         )
302 |         self.assertEqual(response.status_code, 400)
--------------------------------------------------------------------------------
308 |     def test_out_of_range_plot(self) -> None:
    -         response = self.client.post("/api/actions?farm=err",
    -                                     json={"action": "water", "params": {"plot": 999}})
309 +         response = self.client.post(
310 +             "/api/actions?farm=err", json={"action": "water", "params": {"plot": 999}}
311 +         )
312 |         self.assertEqual(response.status_code, 400)
313 |         self.assertEqual(response.json()["error"], "no_such_plot")
314 |
315 |     def test_bad_json_body(self) -> None:
    -         response = self.client.post("/api/actions?farm=err", content=b"{oops",
    -                                     headers={"Content-Type": "application/json"})
316 +         response = self.client.post(
317 +             "/api/actions?farm=err", content=b"{oops", headers={"Content-Type": "application/json"}
318 +         )
319 |         self.assertEqual(response.status_code, 400)
--------------------------------------------------------------------------------
324 |         names = {item["action"] for item in data["actions"]}
    -         self.assertIn("plant", names)        # 有空地 + 有钱 → 可播种
    -         self.assertNotIn("harvest", names)   # 还没种东西 → 不可收获
325 +         self.assertIn("plant", names)  # 有空地 + 有钱 → 可播种
326 +         self.assertNotIn("harvest", names)  # 还没种东西 → 不可收获
327 |
328 |     def test_batch_endpoint(self) -> None:
    -         items = {"actions": [
    -             {"action": "buy_feed", "params": {"quantity": 5}},
    -             {"action": "plant", "params": {"plot": 1, "crop": "wheat"}},
    -             {"action": "harvest", "params": {"plot": 1}},          # 必然失败
    -         ]}
329 +         items = {
330 +             "actions": [
331 +                 {"action": "buy_feed", "params": {"quantity": 5}},
332 +                 {"action": "plant", "params": {"plot": 1, "crop": "wheat"}},
333 +                 {"action": "harvest", "params": {"plot": 1}},  # 必然失败
334 +             ]
335 +         }
336 |         data = self.client.post("/api/batch?farm=multi", json=items).json()
--------------------------------------------------------------------------------
341 |     def test_log_entries_and_since(self) -> None:
    -         self.client.post("/api/actions?farm=logging",
    -                          json={"action": "plant", "params": {"plot": 1, "crop": "wheat"}})
342 +         self.client.post(
343 +             "/api/actions?farm=logging",
344 +             json={"action": "plant", "params": {"plot": 1, "crop": "wheat"}},
345 +         )
346 |         entries = self.client.get("/api/log?farm=logging").json()["entries"]
--------------------------------------------------------------------------------
355 |     def test_farms_list_and_isolation(self) -> None:
    -         self.client.post("/api/actions?farm=alice",
    -                          json={"action": "buy_feed", "params": {"quantity": 10}})
356 +         self.client.post(
357 +             "/api/actions?farm=alice", json={"action": "buy_feed", "params": {"quantity": 10}}
358 +         )
359 |         self.client.post("/api/actions?farm=bob", json={"action": "save"})
360 |         rows = self.client.get("/api/farms").json()["farms"]
361 |         by_name = {row["farm"]: row for row in rows}
362 |         self.assertIn("alice", by_name)
    -         self.assertEqual(by_name["alice"]["gold"],
    -                          config.START_GOLD - 10 * config.FEED_UNIT_PRICE)
363 +         self.assertEqual(by_name["alice"]["gold"], config.START_GOLD - 10 * config.FEED_UNIT_PRICE)
364 |
--------------------------------------------------------------------------------
381 |     def test_admin_save(self) -> None:
    -         self.client.post("/api/actions?farm=saver",
    -                          json={"action": "plant", "params": {"plot": 1, "crop": "wheat"}})
382 +         self.client.post(
383 +             "/api/actions?farm=saver",
384 +             json={"action": "plant", "params": {"plot": 1, "crop": "wheat"}},
385 +         )
386 |         data = self.client.post("/api/admin/save").json()
--------------------------------------------------------------------------------
432 |     def test_action_blocked_without_token(self) -> None:
    -         response = self.client.post("/api/actions",
    -                                    json={"action": "buy_plot"})
433 +         response = self.client.post("/api/actions", json={"action": "buy_plot"})
434 |         self.assertEqual(response.status_code, 401)
--------------------------------------------------------------------------------
448 |         cls.url = f"http://127.0.0.1:{cls.port}"
    -         app = create_app(ServerConfig(host="127.0.0.1", port=cls.port, save_dir=cls.tmp,
    -                                       tick_seconds=600, autosave_interval=3600))
449 +         app = create_app(
450 +             ServerConfig(
451 +                 host="127.0.0.1",
452 +                 port=cls.port,
453 +                 save_dir=cls.tmp,
454 +                 tick_seconds=600,
455 +                 autosave_interval=3600,
456 +             )
457 +         )
458 |         import uvicorn
459 |
    -         cls.server = uvicorn.Server(uvicorn.Config(
    -             app, host="127.0.0.1", port=cls.port, log_level="warning",
    -             access_log=False, lifespan="on",
    -         ))
460 +         cls.server = uvicorn.Server(
461 +             uvicorn.Config(
462 +                 app,
463 +                 host="127.0.0.1",
464 +                 port=cls.port,
465 +                 log_level="warning",
466 +                 access_log=False,
467 +                 lifespan="on",
468 +             )
469 +         )
470 |         cls.thread = threading.Thread(target=cls.server.run, name="live-server", daemon=True)
--------------------------------------------------------------------------------
479 |                 time.sleep(0.2)
    -         else:                                   # pragma: no cover
480 +         else:  # pragma: no cover
481 |             raise unittest.SkipTest("服务器没能在 20 秒内起来")
--------------------------------------------------------------------------------
491 |
    -         return RemoteFarm(endpoint=self.url, farm=farm, realtime=realtime, timeout=8.0,
    -                           autostart=True)
492 +         return RemoteFarm(
493 +             endpoint=self.url, farm=farm, realtime=realtime, timeout=8.0, autostart=True
494 +         )
495 |
--------------------------------------------------------------------------------
506 |             with self.assertRaises(GameError) as ctx:
    -                 client.plant(1, "carrot")          # 地已经占用了
507 +                 client.plant(1, "carrot")  # 地已经占用了
508 |             self.assertEqual(ctx.exception.code, "plot_busy")
509 |
510 |             actions = client.available_actions()
511 |             names = {item["action"] for item in actions}
    -             self.assertIn("plant", names)            # 还有空地可以种
    -             self.assertNotIn("harvest", names)       # 小麦还没成熟
512 +             self.assertIn("plant", names)  # 还有空地可以种
513 +             self.assertNotIn("harvest", names)  # 小麦还没成熟
514 |
    -             client.water(1)                          # 播种后水分 2/3，可以补满
515 +             client.water(1)  # 播种后水分 2/3，可以补满
516 |             self.assertEqual(client.state.plots[0].moisture, config.WATER_CAPACITY)
517 |             with self.assertRaises(GameError) as ctx:
    -                 client.water(1)                      # 水分已满，再浇就是错的
518 +                 client.water(1)  # 水分已满，再浇就是错的
519 |             self.assertEqual(ctx.exception.code, "already_watered")
--------------------------------------------------------------------------------
541 |                 time.sleep(0.1)
    -             self.assertEqual(watcher.state.plots[0].crop, "carrot",
    -                              "实时通道应该把另一方的操作推过来")
542 +             self.assertEqual(
543 +                 watcher.state.plots[0].crop, "carrot", "实时通道应该把另一方的操作推过来"
544 +             )
545 |             self.assertEqual(watcher.mode, "ws")
    |

unformatted: File would be reformatted
   --> tools\preview_tui.py:30:30
    |
29  | from farm.engine import FarmEngine  # noqa: E402
    - from farm.tui import TuiApp         # noqa: E402
30  + from farm.tui import TuiApp  # noqa: E402
31  |
--------------------------------------------------------------------------------
39  |     client = RemoteFarm(endpoint=url, farm=farm, token=token, realtime=False)
    -     app = TuiApp(client, use_color=True, autosave=False,
    -                  server_label=f"{client.farm} @ {client.host}")
40  +     app = TuiApp(
41  +         client, use_color=True, autosave=False, server_label=f"{client.farm} @ {client.host}"
42  +     )
43  |     return client, app
--------------------------------------------------------------------------------
103 |     parser.add_argument("--color", action="store_true", help="保留 ANSI 颜色")
    -     parser.add_argument("--url", default=None,
    -                         help="连到真实服务器渲染（例如 http://127.0.0.1:8765）")
104 +     parser.add_argument(
105 +         "--url", default=None, help="连到真实服务器渲染（例如 http://127.0.0.1:8765）"
106 +     )
107 |     parser.add_argument("--farm", default="default", help="--url 模式下要看的牧场")
--------------------------------------------------------------------------------
116 |         state = engine.snapshot()
    -         print(f"# 来自真实服务器 {args.url} · 牧场 {args.farm} · 第 {state.get('day')} 天 · "
    -               f"金币 {state.get('gold')} · 在线 {state.get('online')}\n")
117 +         print(
118 +             f"# 来自真实服务器 {args.url} · 牧场 {args.farm} · 第 {state.get('day')} 天 · "
119 +             f"金币 {state.get('gold')} · 在线 {state.get('online')}\n"
120 +         )
121 |     else:
    |

unformatted: File would be reformatted
  --> tools\ws_demo.py:41:15
   |
40 |         state = welcome.get("state", {})
   -         print(f"← welcome：牧场 {welcome['farm']} · 第 {state.get('day')} 天 · "
   -               f"金币 {state.get('gold')} · 候选动作 {len(welcome.get('actions', []))} 个")
41 +         print(
42 +             f"← welcome：牧场 {welcome['farm']} · 第 {state.get('day')} 天 · "
43 +             f"金币 {state.get('gold')} · 候选动作 {len(welcome.get('actions', []))} 个"
44 +         )
45 |
--------------------------------------------------------------------------------
65 |                 if kind == "result":
   -                     print(f"← result(id={message.get('id')})：{message.get('message')} "
   -                           f"[通过 WebSocket 提交的动作，ok={message.get('ok')}]")
66 +                     print(
67 +                         f"← result(id={message.get('id')})：{message.get('message')} "
68 +                         f"[通过 WebSocket 提交的动作，ok={message.get('ok')}]"
69 +                     )
70 |                 elif kind == "state":
71 |                     snapshot = message.get("state", {})
   -                     print(f"← state(seq={message.get('seq')})：第 {snapshot.get('day')} 天 · "
   -                           f"金币 {snapshot.get('gold')} · 行动点 {snapshot.get('ap')} · "
   -                           f"在线 {message.get('online')}")
72 +                     print(
73 +                         f"← state(seq={message.get('seq')})：第 {snapshot.get('day')} 天 · "
74 +                         f"金币 {snapshot.get('gold')} · 行动点 {snapshot.get('ap')} · "
75 +                         f"在线 {message.get('online')}"
76 +                     )
77 |                 elif kind == "day":
   |

23 files would be reformatted, 4 files already formatted
