Review fixes: telemetry session id per login; Framework number text

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>
This commit is contained in:
Erik 2026-09-25 13:04:28 +02:00
parent 9b5371c2e4
commit 71f14473c6
10 changed files with 157 additions and 10 deletions

View file

@ -59,6 +59,7 @@ static async Task<int> Record(int port, string outPath)
{
var buffer = new byte[65536];
using var message = new MemoryStream();
string? name = null;
try
{
while (ws.WebSocket.State == WebSocketState.Open && !cts.IsCancellationRequested)
@ -71,6 +72,7 @@ static async Task<int> Record(int port, string outPath)
continue;
string frame = Encoding.UTF8.GetString(message.ToArray());
message.SetLength(0);
name = await Relay.Handle(frame, ws.WebSocket, name);
await writeLock.WaitAsync();
try
{
@ -86,6 +88,7 @@ static async Task<int> Record(int port, string outPath)
catch (Exception ex) when (ex is OperationCanceledException or WebSocketException)
{
}
Relay.Disconnected(name);
Console.WriteLine("client disconnected");
});
}
@ -170,3 +173,82 @@ static bool IsDataMap(string name) => name is "monsters" or "properties" or "ski
or "vitals" or "IntValues" or "DoubleValues" or "BoolValues" or "StringValues" or "offense" or "defense";
static bool LooksLikeData(string key) => key.Length > 0 && (char.IsDigit(key[0]) || key.Contains(' '));
// The backend's vital-sharing relay: a client registers under its character
// name; share_subscribe / share_unsubscribe opt it in and out; every other
// share_* frame goes to every other opted-in client, unchanged.
static class Relay
{
private static readonly object Gate = new();
private static readonly Dictionary<string, WebSocket> Clients = new(StringComparer.Ordinal);
private static readonly HashSet<string> Subscribers = new(StringComparer.Ordinal);
public static async Task<string?> Handle(string frame, WebSocket socket, string? name)
{
string? type;
string? character;
try
{
using JsonDocument doc = JsonDocument.Parse(frame);
JsonElement root = doc.RootElement;
type = root.TryGetProperty("type", out JsonElement t) ? t.GetString() : null;
character = root.TryGetProperty("character_name", out JsonElement c) ? c.GetString()
: root.TryGetProperty("player_name", out JsonElement p) ? p.GetString() : null;
}
catch (JsonException)
{
return name;
}
List<WebSocket> targets = [];
lock (Gate)
{
if (type == "register" && character is not null)
{
Clients[character] = socket;
return character;
}
if (type == "share_subscribe" && character is not null)
{
Subscribers.Add(character);
return name;
}
if (type == "share_unsubscribe" && character is not null)
{
Subscribers.Remove(character);
return name;
}
if (type is null || !type.StartsWith("share_", StringComparison.Ordinal))
return name;
foreach ((string client, WebSocket other) in Clients)
{
if (client != character && Subscribers.Contains(client) && other.State == WebSocketState.Open)
targets.Add(other);
}
}
byte[] bytes = Encoding.UTF8.GetBytes(frame);
foreach (WebSocket target in targets)
{
try
{
await target.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (WebSocketException)
{
}
}
return name;
}
public static void Disconnected(string? name)
{
if (name is null)
return;
lock (Gate)
{
Clients.Remove(name);
Subscribers.Remove(name);
}
}
}