Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work

Continuous Delivery could never be turned off once a crop was planted. Add a
one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool
game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant,
autoreplant claim or Refactor. Autoreplant now also respects crop locks through
the shared _crop_lock predicate. Plan and measurements in happyplanting.md.

Also includes the pending community sync, node network and related work in the
tree; the full suite passed (4999) with all of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 02:23:43 +02:00
co-authored by Claude Sonnet 5.5
parent ce899fc5e1
commit 02ab747cd0
203 changed files with 11624 additions and 301 deletions
+22 -3
View File
@@ -4,15 +4,29 @@ import json
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
class FakeBinary:
def __init__(self, data: bytes, content_type: str = "image/png"):
self.data = data
self.content_type = content_type
class FakeResponse:
def __init__(self, status: int, body):
self.status = status
self.body = body if isinstance(body, str) else json.dumps(body)
self.raw = b""
self.headers = {}
if isinstance(body, FakeBinary):
self.raw, self.body = body.data, ""
self.headers = {"content-type": body.content_type}
else:
self.body = body if isinstance(body, str) else json.dumps(body)
async def text(self):
return self.body
async def read(self):
return self.raw or self.body.encode()
async def json(self):
return json.loads(self.body)
@@ -23,6 +37,8 @@ class FakeFetcher:
self.requests: list[str] = []
self.session = None
self.posts: list[tuple] = []
self.last_headers = None
self.get_headers: list = []
async def __aenter__(self):
return self
@@ -40,6 +56,7 @@ class FakeFetcher:
async def get(self, url, params=None, timeout=None, **kwargs):
key = self.key(url, params)
self.requests.append(key)
self.get_headers.append(kwargs.get("headers"))
route = self.routes.get(key)
if route is None:
return FakeResponse(404, "not found")
@@ -49,10 +66,12 @@ class FakeFetcher:
return FakeResponse(status, body)
async def post(self, url, data=None, timeout=None, **kwargs):
self.posts.append((url, dict(data or {})))
body = dict(data or kwargs.get("json") or {})
self.posts.append((url, body))
self.last_headers = kwargs.get("headers")
route = self.routes.get("post:" + url)
if callable(route):
route = route(dict(data or {}))
route = route(body)
status, body = route if isinstance(route, tuple) else (200, route or "")
return FakeResponse(status, body)