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

@ -58,9 +58,28 @@ internal sealed class FrameworkNumberConverter : JsonConverter
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) =>
throw new NotSupportedException();
/// <summary>
/// .NET Framework's fixed-point and number formats ("F7", "F2", "F0",
/// "N0" ...) for a double: the value taken to fifteen significant digits,
/// then rounded half away from zero to the format's decimals, and never a
/// negative zero. The decimal type formats exactly that way.
/// </summary>
internal static string FrameworkFixed(double value, string format, IFormatProvider provider)
{
if (!double.IsFinite(value))
return value.ToString(format, provider);
string digits = value.ToString("G15", CultureInfo.InvariantCulture);
return decimal.TryParse(digits, NumberStyles.Float, CultureInfo.InvariantCulture, out decimal exact)
? exact.ToString(format, provider)
: value.ToString(format, provider);
}
/// <summary>.NET Framework's "R" for a double: G15 if it reads back exactly, else G17.</summary>
internal static string FrameworkRoundTrip(double value)
{
// The Framework printed a negative zero as "0".
if (value == 0d)
value = 0d;
string text = value.ToString("G15", CultureInfo.InvariantCulture);
if (double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture) == value)
return text;
@ -70,6 +89,8 @@ internal sealed class FrameworkNumberConverter : JsonConverter
/// <summary>.NET Framework's "R" for a float: G7 if it reads back exactly, else G9.</summary>
internal static string FrameworkRoundTrip(float value)
{
if (value == 0f)
value = 0f;
string text = value.ToString("G7", CultureInfo.InvariantCulture);
if (float.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture) == value)
return text;

View file

@ -71,8 +71,13 @@ internal sealed class MmContext
/// <summary>The character's vitae penalty in percent (0 when none), as the original's vitals frame carried it.</summary>
public Func<int> Vitae { get; set; } = static () => 0;
/// <summary>The per-process id the original put in every telemetry frame.</summary>
public static string ProcessSessionId { get; } = Guid.NewGuid().ToString("N");
/// <summary>
/// The telemetry session id: a fresh GUID at every login. The original
/// made one per load of its assembly, and its loader reloaded the assembly
/// at every login; the backend counts new kills per (session id,
/// character), so a login that resets the kill counter needs a new id.
/// </summary>
public string TelemetrySessionId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// Sees every frame the plugin hands to the socket, before the socket

View file

@ -110,6 +110,7 @@ public sealed class MosswartMassacrePlugin : IAcDreamPlugin
string name = _host.Automation.Character.Name;
_context.CharacterName = name;
_context.TelemetrySessionId = Guid.NewGuid().ToString("N");
_context.Settings.Load(name);
_context.IsLoggedIn = true;
_context.Chat.Write("Mosswart Massacre has started!");

View file

@ -71,8 +71,11 @@ internal sealed class ChatRouter : IMmFeature
TimeSpan elapsed = _context.Clock.Now - _kills.StatsStartTime;
string report = string.Format(
CultureInfo.CurrentCulture,
"Total Kills: {0}, Kills per Hour: {1:F2}, Elapsed Time: {2:dd\\.hh\\:mm\\:ss}, Rares Found: {3}",
_kills.TotalKills, _kills.KillsPerHour, elapsed, _rares.RareCount);
"Total Kills: {0}, Kills per Hour: {1}, Elapsed Time: {2:dd\\.hh\\:mm\\:ss}, Rares Found: {3}",
_kills.TotalKills,
FrameworkNumberConverter.FrameworkFixed(_kills.KillsPerHour, "F2", CultureInfo.CurrentCulture),
elapsed,
_rares.RareCount);
_context.Host.Log.Info($"[Mosswart Massacre] Reporting to allegiance: {report}");
_context.Automation.Chat.Submit($"/a {report}");
}

View file

@ -102,9 +102,10 @@ internal sealed class SpawnPortalStream : IMmFeature
private static (string Ns, string Ew, string Z) Format(in PluginNavigationPosition position)
{
const string fmt = "F7";
string ns = position.NorthSouth.ToString(fmt, CultureInfo.InvariantCulture);
string ew = position.EastWest.ToString(fmt, CultureInfo.InvariantCulture);
string z = ((float)(position.Elevation * 240d)).ToString("F2", CultureInfo.InvariantCulture);
string ns = FrameworkNumberConverter.FrameworkFixed(position.NorthSouth, fmt, CultureInfo.InvariantCulture);
string ew = FrameworkNumberConverter.FrameworkFixed(position.EastWest, fmt, CultureInfo.InvariantCulture);
// The original read the height as a float and formatted it widened to a double.
string z = FrameworkNumberConverter.FrameworkFixed((double)(float)(position.Elevation * 240d), "F2", CultureInfo.InvariantCulture);
return (ns, ew, z);
}
}

View file

@ -64,13 +64,13 @@ internal sealed class TelemetryStream : IMmFeature, IBackendConnectionListener
type = "telemetry",
character_name = _context.CharacterName,
char_tag = _context.Settings.Current.CharTag ?? "",
session_id = MmContext.ProcessSessionId,
session_id = _context.TelemetrySessionId,
timestamp = Wire.Timestamp(_context.Clock),
ew = coords.EW,
ns = coords.NS,
z = coords.Z,
kills = kills.TotalKills,
kills_per_hour = kills.KillsPerHour.ToString("F0", CultureInfo.CurrentCulture),
kills_per_hour = FrameworkNumberConverter.FrameworkFixed(kills.KillsPerHour, "F0", CultureInfo.CurrentCulture),
onlinetime = (_context.Clock.Now - kills.StatsStartTime).ToString(@"dd\.hh\:mm\:ss", CultureInfo.InvariantCulture),
deaths = kills.SessionDeaths.ToString(CultureInfo.CurrentCulture),
total_deaths = kills.TotalDeaths.ToString(CultureInfo.CurrentCulture),