This commit is contained in:
@@ -24,6 +24,8 @@ export interface ScalingMember {
|
||||
identity: string;
|
||||
state: 'occupied' | 'empty' | 'creating' | 'removing' | 'stopped' | 'unknown' | 'protected';
|
||||
players: number | null;
|
||||
/** Current occupied match round from fresh telemetry; null when unavailable. */
|
||||
round: number | null;
|
||||
observedAt: number | null;
|
||||
source: string | null;
|
||||
emptySince: number | null;
|
||||
|
||||
@@ -58,7 +58,10 @@ export interface FleetOperation {
|
||||
export type FleetFilter = 'all' | 'occupied' | 'empty' | 'attention' | 'testing' | 'pinned';
|
||||
export type FleetSort = 'name' | 'players' | 'round' | 'duration';
|
||||
|
||||
export function freshServer(server: FleetServer, now: number): boolean {
|
||||
export function freshServer(
|
||||
server: Pick<FleetServer, 'health' | 'last_error' | 'last_seen_at'>,
|
||||
now: number
|
||||
): boolean {
|
||||
return (
|
||||
server.health === 'online' &&
|
||||
!server.last_error &&
|
||||
@@ -93,7 +96,18 @@ export function fleetIdentity(
|
||||
}
|
||||
|
||||
export function currentRound(
|
||||
server: FleetServer,
|
||||
server: Pick<
|
||||
FleetServer,
|
||||
| 'telemetry'
|
||||
| 'telemetry_after'
|
||||
| 'health'
|
||||
| 'last_error'
|
||||
| 'last_seen_at'
|
||||
| 'last_players'
|
||||
| 'active_since'
|
||||
| 'game_started_at'
|
||||
| 'game_ended_at'
|
||||
>,
|
||||
report: GameTelemetry | undefined,
|
||||
now: number
|
||||
): number | null {
|
||||
|
||||
@@ -120,6 +120,21 @@ export function planScaling(
|
||||
)
|
||||
return result('wait', 'Waiting for fresh occupancy evidence.');
|
||||
if (occupied + empty < desired) {
|
||||
// Brief joins do not increase spare demand until their match reaches round two.
|
||||
// Keep the ordinary desired count for reporting and surplus removal so a
|
||||
// new match or missing telemetry cannot retire an existing spare.
|
||||
const established = members.filter(
|
||||
(m) => m.state === 'occupied' && m.round !== null && m.round >= 2
|
||||
).length;
|
||||
const growthTarget = Math.min(
|
||||
policy.maximum,
|
||||
Math.max(policy.minimum, established + policy.spares)
|
||||
);
|
||||
if (occupied + empty >= growthTarget)
|
||||
return result(
|
||||
'wait',
|
||||
'Waiting for occupied lobbies to reach round 2 before adding a spare.'
|
||||
);
|
||||
if (snapshot.reservedNames.length >= policy.maximum)
|
||||
return result('wait', 'Maximum lobby reservations reached.');
|
||||
if (!snapshot.keyPool.available)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { listServers, type ServerRow } from '../fleet/servers.js';
|
||||
import { observationReader } from '../fleet/status.js';
|
||||
import { parseServerName, zombieMaps } from '../../map-aliases.js';
|
||||
import { freshStatusTimestamp } from '../../server-freshness.js';
|
||||
import { currentRound } from '../../fleet-overview.js';
|
||||
import type { ScalingMember, ScalingPolicy, ScalingSnapshot } from '../../autoscaling.js';
|
||||
import { emptyObservation, type EmptyObservation } from './planner.js';
|
||||
|
||||
@@ -164,6 +165,12 @@ export function scalingSnapshot(
|
||||
identity,
|
||||
state,
|
||||
players,
|
||||
round:
|
||||
state === 'occupied' &&
|
||||
observation.telemetry &&
|
||||
Date.parse(observation.telemetry.receivedAt) <= now
|
||||
? currentRound(observation, observation.telemetry, now)
|
||||
: null,
|
||||
observedAt,
|
||||
source: source ?? null,
|
||||
emptySince: null,
|
||||
@@ -181,6 +188,7 @@ export function scalingSnapshot(
|
||||
identity: 'reserved',
|
||||
state: 'creating',
|
||||
players: null,
|
||||
round: null,
|
||||
observedAt: null,
|
||||
source: null,
|
||||
emptySince: null,
|
||||
|
||||
Reference in New Issue
Block a user