Fix WebSocket disconnect crash and missing 'personality' field

Two bugs that crashed the engine in production:

1. world.nearby_agents() did not SELECT the 'personality' column, so
   _reaction_turn raised KeyError on every reactive trigger, killing the
   engine thread silently. Engine-Thread stieg aus ohne Log.

   Fix: select personality and json-parse it so callers get a real list,
   matching agents_mod.get().

2. server.py ws() handler caught the generic 'Exception' from
   asyncio.to_thread(queue.get) and tried to send a ping back, but the
   WebSocket was already closed by the client. Starlette raised
   RuntimeError: Cannot call 'send' once a close message has been sent.

   Fix: drop the ping, just break the loop on any exception. Client
   disconnect now handled cleanly.

Live-verified: 0 errors in log after 3 abrupt disconnects, engine
continues producing ticks.
This commit is contained in:
Jeuners 2026-06-15 02:04:49 +02:00
parent 887c913bcd
commit 8a52e3dfa3
2 changed files with 12 additions and 3 deletions

View file

@ -95,7 +95,8 @@ def nearby_agents(agent_id: str, x: int, y: int, radius: float = HEARING_DISTANC
c.row_factory = sqlite3.Row
try:
rows = c.execute(
"SELECT id,name,x,y,energy FROM agents WHERE id!=? AND alive=1", (agent_id,)
"SELECT id,name,personality,x,y,energy FROM agents "
"WHERE id!=? AND alive=1", (agent_id,)
).fetchall()
out = []
for r in rows:
@ -103,6 +104,12 @@ def nearby_agents(agent_id: str, x: int, y: int, radius: float = HEARING_DISTANC
if d <= radius:
d2 = dict(r)
d2["distance"] = d
# personality is stored as a JSON string; parse so callers
# get a real list (matches agents_mod.get).
try:
d2["personality"] = json.loads(d2.get("personality") or "[]")
except Exception:
d2["personality"] = []
out.append(d2)
return out
finally:

View file

@ -164,8 +164,10 @@ async def ws(ws: WebSocket):
try:
msg = await asyncio.to_thread(queue.get, timeout=30.0)
await ws.send_json(msg)
except WebSocketDisconnect:
break
except Exception:
# heartbeat
await ws.send_json({"type": "ping", "ts": time.time()})
# client went away — stop sending
break
except WebSocketDisconnect:
pass