On Linux, aborting the server-side WebSocket left the TCP connection open,
so the client never saw the drop and the reconnect test timed out on the
Gitea runner. Ending the response closes it everywhere, as a real server's
drop does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live, /mm ws enable printed nothing about the connection: the socket's
connecting / CONNECTED / REGISTERED / error / reconnect / DISABLED lines went
only to the host log, which the windowed client does not show. The original
printed every one of them to chat.
BackendClient now queues each status and error line (original wording) and
still logs it; BackendSession drains the queue to chat on the tick, and right
after a start or stop on the tick thread so "[WebSocket] connecting…" and
"[WebSocket] DISABLED" come before the command's own reply, as in the
original. The socket thread never writes to chat. The original's telemetry-loop
and cleanup lines are emitted at the matching points, "Starting telemetry
loop" comes from the telemetry listener, and "Vital sharing re-subscribed" is
no longer verbose-only. No frame on the wire changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F2: the logoff inventory is skipped when the host already let go of the
items (it may reset the session before reporting the logoff): an empty list
would have emptied the backend's inventory and overwritten the saved copy.
F3/F4: each socket start owns its run; Stop no longer blocks the game thread
and gives the run up to ten seconds to send what was queued (a large logoff
inventory) before closing, flushing that run's own send loop; a relog never
hears the previous run's frames.
F7: on connect, share_subscribe goes out before the first telemetry frame, as
the original sent them.
F8: a rare announcement still waiting at logoff is dropped rather than sent
as the next character.
F9: each dungeon map is sent once per login, as the original (reloaded per
login) did.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
F1: the telemetry session id is new at every login. The original made one per
load of its assembly and its loader reloaded it at every login; the backend
counts new kills per (session id, character), so reusing one id across a
relog under-counted kills until the reset counter passed the old total.
F5/F6: .NET Framework never wrote a negative zero, and its fixed-point
formats (F7, F2, F0) rounded at fifteen significant digits, half away from
zero. Spawn and portal coordinates, kills_per_hour and the !report line now
format that way; the round-trip converter maps -0 to 0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The original ran Newtonsoft.Json on .NET Framework, whose "R" format writes
15 significant digits when they read back exactly and 17 otherwise (floats: 7,
else 9). Modern .NET writes the shortest round trip instead, so a value like
-0.066666670143604279 came out as -0.06666667014360428: equal when parsed,
different bytes. Every frame now goes through a converter that repeats the
Framework rule, with Newtonsoft's '.0' suffix and non-finite handling kept.
Goldens that encoded the modern digits are corrected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The backend's quest panel shows the stipend, blank aug gem and insatiable
jaw timers from the `quest` frame, so the port reads /myquests the way the
original did (a three-second window of chat lines matched by its regex)
and streams the three priority stamps every 30 s with the same five-field
frame and the same countdown strings. The name table is copied as it was,
so the duplicate insatiableeaterjaw entry still resolves to the later name.
Timers run on the tick scheduler instead of thread-pool timers.
The session tests clear the login-time /myquests before counting the
commands the backend ran.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The socket connects with the shared-secret header, registers first, reassembles
incoming frames of any size (the original dropped anything over 4 KiB) and
reconnects two seconds after a failure. Incoming share_* frames go to the
vital-sharing handlers; any other frame is a chat-box command for this
character, run one per tick through the chat bar as if typed. Frames are
written with Newtonsoft.Json defaults, the original's serializer, so the
backend sees the same bytes. The secret lives in the character's settings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>