Wait for round two before adding spare lobbies
CI / checks (push) Successful in 7m20s

This commit is contained in:
2026-10-15 20:40:03 -04:00
parent 68616327ec
commit c1758cce25
11 changed files with 298 additions and 7 deletions
+44 -1
View File
@@ -22,6 +22,7 @@ const member = (n: number, state: ScalingMember['state'] = 'empty'): ScalingMemb
identity: `instance:${n}`,
state,
players: state === 'occupied' ? 1 : state === 'empty' ? 0 : null,
round: state === 'occupied' ? 2 : null,
observedAt: now,
source: 'rcon',
emptySince: 1,
@@ -49,7 +50,7 @@ describe('autoscaling policy planning', () => {
expect(() => scalingSettings(input)).toThrow();
expect(scalingSettings({ game: 't6', mapSlug: 'town', maximum: 4 }).mode).toBe('off');
});
it('creates a spare even with free player slots and bootstraps an empty map', () => {
it('creates a spare at round two even with free player slots and bootstraps an empty map', () => {
expect(planScaling(policy, snapshot(member(1, 'occupied')), now)).toMatchObject({
action: 'create',
server: name(2),
@@ -63,6 +64,48 @@ describe('autoscaling policy planning', () => {
'wait'
);
});
it.each([null, 0, 1])('waits for round two when the current round is %s', (round) => {
expect(
planScaling(policy, snapshot({ ...member(1, 'occupied'), round }), now)
).toMatchObject({
action: 'wait',
server: null,
desired: 2,
deficit: 1,
occupied: 1,
reason: 'Waiting for occupied lobbies to reach round 2 before adding a spare.'
});
expect(
planScaling(policy, snapshot({ ...member(1, 'occupied'), round }, member(2)), now)
).toMatchObject({ action: 'wait', desired: 2, deficit: 0 });
});
it.each([2, 10])('replenishes a spare when each occupied lobby reaches round %i', (round) => {
const established = { ...member(1, 'occupied'), round: 10 };
expect(
planScaling(policy, snapshot(established, { ...member(2, 'occupied'), round: 1 }), now)
).toMatchObject({ action: 'wait', desired: 3 });
expect(
planScaling(policy, snapshot(established, { ...member(2, 'occupied'), round }), now)
).toMatchObject({ action: 'create', server: name(3), desired: 3 });
});
it('uses established occupancy regardless of lobby number', () => {
expect(planScaling(policy, snapshot(member(4, 'occupied')), now)).toMatchObject({
action: 'create',
server: name(1)
});
});
it.each([{ minimum: 3 }, { spares: 2 }])(
'maintains baseline capacity before round two: %j',
(settings) => {
expect(
planScaling(
{ ...policy, ...settings },
snapshot({ ...member(1, 'occupied'), round: 1 }),
now
)
).toMatchObject({ action: 'create', server: name(2) });
}
);
it('selects the first free identity independently of the game key pool size', () => {
expect(
planScaling(