fix: skip deleted reserved turn queues during flush

This commit is contained in:
2026-08-05 01:21:16 +00:00
parent a65d630d6f
commit 6eba8e24c3
3 changed files with 75 additions and 3 deletions
+26 -3
View File
@@ -26,7 +26,7 @@ import { asRecord } from '@sammo-ts/common';
import type { TurnDaemonCommandResult, TurnDaemonHooks } from '../lifecycle/types.js';
import type { InMemoryTurnWorld } from './inMemoryWorld.js';
import type { InMemoryReservedTurnStore } from './reservedTurnStore.js';
import type { InMemoryReservedTurnStore, ReservedTurnChanges } from './reservedTurnStore.js';
import { buildDiplomacyMeta } from '@sammo-ts/logic';
import { ensureItemInventory, withSerializedItemInventory } from '@sammo-ts/logic/items/index.js';
import { persistGeneralLifecycleEvents } from './generalTurnLifecyclePersistence.js';
@@ -43,6 +43,26 @@ export interface DatabaseTurnHooks {
close(): Promise<void>;
}
export const excludeDeletedReservedTurnQueues = (
changes: ReservedTurnChanges,
deletedGeneralIds: readonly number[],
deletedNationIds: readonly number[]
): ReservedTurnChanges => {
const deletedGenerals = new Set(deletedGeneralIds);
const deletedNations = new Set(deletedNationIds);
const keepGeneral = (generalId: number): boolean => !deletedGenerals.has(generalId);
const keepNation = (key: string): boolean => !deletedNations.has(Number(key.split(':', 1)[0]));
return {
generalIds: changes.generalIds.filter(keepGeneral),
generalInitializationIds: changes.generalInitializationIds.filter(keepGeneral),
generalLeaseIds: changes.generalLeaseIds.filter(keepGeneral),
nationKeys: changes.nationKeys.filter(keepNation),
nationInitializationKeys: changes.nationInitializationKeys.filter(keepNation),
nationLeaseKeys: changes.nationLeaseKeys.filter(keepNation),
};
};
const asJson = (value: unknown): InputJsonValue => value as InputJsonValue;
const formatLegacyNumber = (value: number): string => Math.round(value).toLocaleString('en-US');
@@ -602,6 +622,9 @@ export const createDatabaseTurnHooks = async (
pendingUnificationFinalizations,
} = changes;
const reservedTurnChanges = options?.reservedTurns?.peekDirtyState();
const persistedReservedTurnChanges = reservedTurnChanges
? excludeDeletedReservedTurnQueues(reservedTurnChanges, deletedGenerals, deletedNations)
: undefined;
const worldStateUpdate: TurnEngineWorldStateUpdateInput = {
currentYear: state.currentYear,
@@ -1010,8 +1033,8 @@ export const createDatabaseTurnHooks = async (
message
);
}
if (options?.reservedTurns && reservedTurnChanges) {
await options.reservedTurns.persistChanges(prisma, reservedTurnChanges);
if (options?.reservedTurns && persistedReservedTurnChanges) {
await options.reservedTurns.persistChanges(prisma, persistedReservedTurnChanges);
}
if (commandCompletion) {
await prisma.inputEvent.update({
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest';
import { excludeDeletedReservedTurnQueues } from '../src/turn/databaseHooks.js';
describe('excludeDeletedReservedTurnQueues', () => {
it('omits dirty, initialization, and leased queues owned by deleted entities', () => {
expect(
excludeDeletedReservedTurnQueues(
{
generalIds: [1, 2],
generalInitializationIds: [2, 3],
generalLeaseIds: [1, 2, 3],
nationKeys: ['10:12', '20:11'],
nationInitializationKeys: ['20:9', '30:12'],
nationLeaseKeys: ['10:12', '20:11', '30:12'],
},
[2],
[20]
)
).toEqual({
generalIds: [1],
generalInitializationIds: [3],
generalLeaseIds: [1, 3],
nationKeys: ['10:12'],
nationInitializationKeys: ['30:12'],
nationLeaseKeys: ['10:12', '30:12'],
});
});
});
@@ -524,6 +524,12 @@ integration('RaiseInvader database persistence', () => {
).toBe(30);
}
await reservedTurns.prepareTurnsForExecution(firstCreatedGeneralId, {
nationId: createdNationId,
officerLevel: 12,
});
reservedTurns.shiftGeneralTurns(firstCreatedGeneralId, -1);
reservedTurns.shiftNationTurns(createdNationId, 12, -1);
for (const generalId of createdGeneralIds) {
expect(world.removeGeneral(generalId)).toBe(true);
}
@@ -539,6 +545,20 @@ integration('RaiseInvader database persistence', () => {
});
expect(await db.event.findUnique({ where: { id: createdEventIds[1]! } })).toBeNull();
expect(await db.generalTurnRevision.findUnique({ where: { generalId: firstCreatedGeneralId } })).toBeNull();
expect(
await db.nationTurnRevision.findUnique({
where: {
nationId_officerLevel: { nationId: createdNationId, officerLevel: 12 },
},
})
).toBeNull();
expect(reservedTurns.inspectState()).toMatchObject({
dirtyGeneralIds: [],
dirtyNationKeys: [],
leasedGeneralIds: [],
leasedNationKeys: [],
});
expect(await db.worldState.findUniqueOrThrow({ where: { id: stateRow.id } })).toMatchObject({
meta: expect.objectContaining({ isunited: 3, isUnited: 3, refreshLimit: 300 }),
});