Remove Dead Ops Arcade support

This commit is contained in:
2026-10-07 15:44:19 -04:00
parent db92c89b05
commit c9a604303d
6 changed files with 25 additions and 5 deletions
+2
View File
@@ -4,6 +4,8 @@ Open **Fleet → Autoscaling** to keep empty lobbies available for new groups. P
Autoscaling is opt-in. Installing the feature creates no policies and changes no lobby counts. Use a two-week dry run before enabling live actions. Autoscaling is opt-in. Installing the feature creates no policies and changes no lobby counts. Use a two-week dry run before enabling live actions.
T5 game-wide policies cover the ten standard Zombies maps. Dead Ops Arcade is not supported.
## Configure a policy ## Configure a policy
Choose **Add policy**, select the game and scope, and enter an explicit maximum. The initial editor selects Dry run, with a minimum of one running lobby, one empty spare, a 20-minute empty interval, and a five-minute action cooldown. Counts must be between 1 and 20; the minimum and spare target cannot exceed the maximum. Choose **Add policy**, select the game and scope, and enter an explicit maximum. The initial editor selects Dry run, with a minimum of one running lobby, one empty spare, a 20-minute empty interval, and a five-minute action cooldown. Counts must be between 1 and 20; the minimum and spare target cannot exceed the maximum.
-1
View File
@@ -27,7 +27,6 @@ export const T5_ZOMBIE_MAPS: readonly T5MapEntry[] = Object.freeze([
{ friendly: 'Shangri-La', slug: 'temple', canonical: 'zombie_temple' }, { friendly: 'Shangri-La', slug: 'temple', canonical: 'zombie_temple' },
{ friendly: 'Moon', slug: 'moon', canonical: 'zombie_moon' }, { friendly: 'Moon', slug: 'moon', canonical: 'zombie_moon' },
{ friendly: 'Five', slug: 'pentagon', canonical: 'zombie_pentagon' }, { friendly: 'Five', slug: 'pentagon', canonical: 'zombie_pentagon' },
{ friendly: 'Dead Ops Arcade', slug: 'zombietron', canonical: 'zombietron' },
{ friendly: 'Nacht Der Untoten', slug: 'prototype', canonical: 'zombie_cod5_prototype' }, { friendly: 'Nacht Der Untoten', slug: 'prototype', canonical: 'zombie_cod5_prototype' },
{ friendly: 'Verrückt', slug: 'asylum', canonical: 'zombie_cod5_asylum' }, { friendly: 'Verrückt', slug: 'asylum', canonical: 'zombie_cod5_asylum' },
{ friendly: 'Shi No Numa', slug: 'sumpf', canonical: 'zombie_cod5_sumpf' }, { friendly: 'Shi No Numa', slug: 'sumpf', canonical: 'zombie_cod5_sumpf' },
+12
View File
@@ -10,6 +10,18 @@ beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); });
afterEach(async () => { await fixture.cleanup(); }); afterEach(async () => { await fixture.cleanup(); });
const settings = { game: 't6', mapSlug: '*', maximum: 2, mode: 'create-only' }; const settings = { game: 't6', mapSlug: '*', maximum: 2, mode: 'create-only' };
const maps = zombieMaps('t6'); const maps = zombieMaps('t6');
it('satisfies a T5 game-wide policy with the ten standard Zombies maps', () => {
const now = 2_000_000;
const policy: ScalingPolicy = { ...scalingSettings({ ...settings, game: 't5' }),
id: 'policy', revision: 1, nextActionAt: 0, pausedReason: null, updatedAt: '' };
const members: ScalingMember[] = zombieMaps('t5').map(map => ({ name: `t5_zombie_${map.slug}_1`, mapSlug: map.slug,
identity: map.slug, state: 'empty', players: 0, observedAt: now, source: 'rcon', emptySince: 1, blocker: null, operationId: null }));
const snapshot = { members, reservedNames: members.map(m => m.name), keyPool: { total: 20, available: 10 }, blockers: [] };
expect(planScaling(policy, snapshot, now)).toMatchObject({ action: 'wait', server: null, desired: 10, deficit: 0, empty: 10 });
expect(() => scalingSettings({ ...settings, game: 't5', mapSlug: 'zombietron' })).toThrow('supported game and map');
});
async function setup() { async function setup() {
await prepareNativeHost(); await prepareNativeHost();
const { db } = await import('../../src/lib/server/db/index.js'); const { db } = await import('../../src/lib/server/db/index.js');
@@ -89,7 +89,7 @@ describe('matching a native collection', () => {
const { saveBlueprint } = await import('../../src/lib/server/fleet/launch-recipes.js'); const { saveBlueprint } = await import('../../src/lib/server/fleet/launch-recipes.js');
const { previewCollection, queueCollection, getCollection } = await import('../../src/lib/server/fleet/collections.js'); const { previewCollection, queueCollection, getCollection } = await import('../../src/lib/server/fleet/collections.js');
const { T5_ZOMBIE_MAPS } = await import('../../src/lib/map-aliases.js'); const { T5_ZOMBIE_MAPS } = await import('../../src/lib/map-aliases.js');
const blueprint = saveBlueprint({ name: 'Replace nine with ten', entries: T5_ZOMBIE_MAPS.filter(map => map.slug !== 'zombietron') const blueprint = saveBlueprint({ name: 'Replace nine with ten', entries: T5_ZOMBIE_MAPS
.map(map => ({ game: 't5', mapSlug: map.slug, count: 1 })) }); .map(map => ({ game: 't5', mapSlug: map.slug, count: 1 })) });
const preview = previewCollection(blueprint.id, 'exact'); const preview = previewCollection(blueprint.id, 'exact');
const queued = queueCollection({ previewId: preview.id, const queued = queueCollection({ previewId: preview.id,
@@ -137,7 +137,7 @@ describe('matching a native collection', () => {
const { GET: fleetResponse } = await import('../../src/routes/api/servers/+server.js'); const { GET: fleetResponse } = await import('../../src/routes/api/servers/+server.js');
const { POST: agentHeartbeat } = await import('../../src/routes/api/agent/heartbeat/+server.js'); const { POST: agentHeartbeat } = await import('../../src/routes/api/agent/heartbeat/+server.js');
const { T5_ZOMBIE_MAPS } = await import('../../src/lib/map-aliases.js'); const { T5_ZOMBIE_MAPS } = await import('../../src/lib/map-aliases.js');
const blueprint = saveBlueprint({ name: 'Match ten T5 lobbies', entries: T5_ZOMBIE_MAPS.filter(map => map.slug !== 'zombietron') const blueprint = saveBlueprint({ name: 'Match ten T5 lobbies', entries: T5_ZOMBIE_MAPS
.map(map => ({ game: 't5', mapSlug: map.slug, count: 1 })) }); .map(map => ({ game: 't5', mapSlug: map.slug, count: 1 })) });
const preview = previewCollection(blueprint.id, 'exact'); const preview = previewCollection(blueprint.id, 'exact');
expect(preview).toMatchObject({ ready: true, mode: 'exact' }); expect(preview).toMatchObject({ ready: true, mode: 'exact' });
+2 -2
View File
@@ -106,8 +106,8 @@ describe('automatic map selection', () => {
}); });
it('appends the rotation when absent and preserves CRLF', () => { it('appends the rotation when absent and preserves CRLF', () => {
expect(composeServerCfg('set sv_voice "1"\r\n', [], 't5', 'zombietron')) expect(composeServerCfg('set sv_voice "1"\r\n', [], 't5', 'zombie_moon'))
.toBe('set sv_voice "1"\r\n// Map selected by Electric Cherry\r\nset sv_maprotation "map zombietron"\r\n'); .toBe('set sv_voice "1"\r\n// Map selected by Electric Cherry\r\nset sv_maprotation "map zombie_moon"\r\n');
}); });
it('removes conflicting duplicate assignments regardless of case or command form', () => { it('removes conflicting duplicate assignments regardless of case or command form', () => {
+7
View File
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { import {
parseServerName, parseServerName,
selectedZombieMap,
t5ZombieMapBySlug, t5ZombieMapBySlug,
T5_ZOMBIE_MAPS, T5_ZOMBIE_MAPS,
T6_ZOMBIE_MAPS T6_ZOMBIE_MAPS
@@ -98,6 +99,12 @@ describe('parseServerName (new naming: t5_<mode>_<slug>_<n>)', () => {
}); });
describe('t5ZombieMapBySlug', () => { describe('t5ZombieMapBySlug', () => {
it('excludes Dead Ops Arcade from supported map selection', () => {
expect(t5ZombieMapBySlug('zombietron')).toBeUndefined();
expect(selectedZombieMap('t5', 'zombietron')).toBeUndefined();
expect(parseServerName('t5_zombie_zombietron_1')?.mapCanonical).toBeNull();
});
it('resolves each catalog slug to its canonical + friendly', () => { it('resolves each catalog slug to its canonical + friendly', () => {
for (const entry of T5_ZOMBIE_MAPS) { for (const entry of T5_ZOMBIE_MAPS) {
const found = t5ZombieMapBySlug(entry.slug); const found = t5ZombieMapBySlug(entry.slug);