Normalize T6 hexadecimal RCON GUIDs for ping matching
This commit is contained in:
@@ -20,6 +20,7 @@ export interface GameObservation {
|
||||
}
|
||||
export interface ServerObservation extends GameObservation {
|
||||
name: string;
|
||||
game?: string | null;
|
||||
health: 'online' | 'stale' | 'offline' | 'starting' | 'provisioning';
|
||||
container_state?: string | null;
|
||||
container_checked_at?: string | null;
|
||||
@@ -51,6 +52,15 @@ function playerMatchName(name: string): string {
|
||||
return name.replace(/\^\d/g, '').trim();
|
||||
}
|
||||
|
||||
function playerMatchGuid(guid: string, game: string | null | undefined, source: 'rcon' | 'telemetry'): string {
|
||||
if (game !== 't6') return guid;
|
||||
// T6 status prints hexadecimal account IDs; telemetry sends decimal IDs.
|
||||
// Keep integer precision for IDs beyond Number.MAX_SAFE_INTEGER.
|
||||
if (source === 'rcon' && /^(?:0x)?[0-9a-f]+$/i.test(guid)) return BigInt(`0x${guid.replace(/^0x/i, '')}`).toString();
|
||||
if (source === 'telemetry' && /^\d+$/.test(guid)) return BigInt(guid).toString();
|
||||
return guid;
|
||||
}
|
||||
|
||||
/** Display policy: use one current roster/count source, keeping raw RCON for diagnostics/fallback. */
|
||||
export function selectServerObservation<T extends ServerObservation>(server: T, report: GameTelemetry | undefined, now: number): T {
|
||||
if (server.telemetry && (!report || Date.parse(server.telemetry.receivedAt) > Date.parse(report.receivedAt))) report = server.telemetry;
|
||||
@@ -69,10 +79,11 @@ export function selectServerObservation<T extends ServerObservation>(server: T,
|
||||
// Telemetry owns roster membership. Supplement missing pings only with
|
||||
// unambiguous RCON identities from the current map and server lifecycle.
|
||||
const pings = new Map<string, number | null>();
|
||||
const guidKey = (guid: string, source: 'rcon' | 'telemetry') => playerMatchGuid(guid, server.game, source);
|
||||
if (fresh && raw.last_map === report.map) {
|
||||
const byName = new Map<string, StatusPlayer | null>();
|
||||
const nameCounts = new Map<string, number>();
|
||||
const telemetryGuids = new Set(report.players.map(player => player.guid));
|
||||
const telemetryGuids = new Set(report.players.map(player => guidKey(player.guid, 'telemetry')));
|
||||
for (const player of report.players) {
|
||||
const name = playerMatchName(player.name);
|
||||
nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
|
||||
@@ -81,18 +92,22 @@ export function selectServerObservation<T extends ServerObservation>(server: T,
|
||||
const name = playerMatchName(player.name);
|
||||
if (name) byName.set(name, byName.has(name) ? null : player);
|
||||
const ping = player.ping;
|
||||
if (player.guid) pings.set(player.guid, pings.has(player.guid) || ping === null || !Number.isSafeInteger(ping) || ping < 0 ? null : ping);
|
||||
if (player.guid) {
|
||||
const guid = guidKey(player.guid, 'rcon');
|
||||
pings.set(guid, pings.has(guid) || ping === null || !Number.isSafeInteger(ping) || ping < 0 ? null : ping);
|
||||
}
|
||||
}
|
||||
for (const player of report.players) {
|
||||
// A GUID match always wins, including ambiguous/invalid readings.
|
||||
if (pings.has(player.guid)) continue;
|
||||
const guid = guidKey(player.guid, 'telemetry');
|
||||
if (pings.has(guid)) continue;
|
||||
const name = playerMatchName(player.name);
|
||||
const match = byName.get(name);
|
||||
// Names must be unique in both rosters. Never borrow a player
|
||||
// already identified by another telemetry player's GUID.
|
||||
if (!match || nameCounts.get(name) !== 1 || (match.guid && telemetryGuids.has(match.guid))) continue;
|
||||
if (!match || nameCounts.get(name) !== 1 || (match.guid && telemetryGuids.has(guidKey(match.guid, 'rcon')))) continue;
|
||||
const ping = match.ping;
|
||||
if (ping !== null && Number.isSafeInteger(ping) && ping >= 0) pings.set(player.guid, ping);
|
||||
if (ping !== null && Number.isSafeInteger(ping) && ping >= 0) pings.set(guid, ping);
|
||||
}
|
||||
}
|
||||
// New reports carry persisted anchors through both SSE and HTTP. Keep
|
||||
@@ -106,7 +121,7 @@ export function selectServerObservation<T extends ServerObservation>(server: T,
|
||||
return { ...result, health: 'online', status_source: 'telemetry', last_players: players,
|
||||
last_map: report.map, last_seen_at: report.receivedAt, last_error: null,
|
||||
roster: report.players.map(player => ({ num: null, score: 'score' in player ? player.score : null,
|
||||
ping: player.ping ?? pings.get(player.guid) ?? null, guid: player.guid, name: player.name })),
|
||||
ping: player.ping ?? pings.get(guidKey(player.guid, 'telemetry')) ?? null, guid: player.guid, name: player.name })),
|
||||
active_since: players > 0 ? timing.activeSince : null,
|
||||
inactive_since: players === 0 ? timing.inactiveSince : null,
|
||||
game_started_at: players === 0 ? null : timing.gameStartedAt,
|
||||
|
||||
Reference in New Issue
Block a user