fix invader lifecycle history parity
This commit is contained in:
@@ -443,7 +443,7 @@ export const createRaiseInvaderHandler = (options: {
|
|||||||
scope: LogScope.SYSTEM,
|
scope: LogScope.SYSTEM,
|
||||||
category: LogCategory.HISTORY,
|
category: LogCategory.HISTORY,
|
||||||
text,
|
text,
|
||||||
format: LogFormat.NOTICE_YEAR_MONTH,
|
format: LogFormat.YEAR_MONTH,
|
||||||
year: environment.year,
|
year: environment.year,
|
||||||
month: environment.month,
|
month: environment.month,
|
||||||
});
|
});
|
||||||
@@ -524,7 +524,7 @@ export const createInvaderEndingHandler = (options: {
|
|||||||
scope: LogScope.SYSTEM,
|
scope: LogScope.SYSTEM,
|
||||||
category: LogCategory.HISTORY,
|
category: LogCategory.HISTORY,
|
||||||
text,
|
text,
|
||||||
format: LogFormat.NOTICE_YEAR_MONTH,
|
format: LogFormat.YEAR_MONTH,
|
||||||
year: environment.year,
|
year: environment.year,
|
||||||
month: environment.month,
|
month: environment.month,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||||
import { createGamePostgresConnector, type GamePrismaClient } from '@sammo-ts/infra';
|
import { createGamePostgresConnector, type GamePrismaClient } from '@sammo-ts/infra';
|
||||||
import type { City, Nation } from '@sammo-ts/logic';
|
import type { City, Nation } from '@sammo-ts/logic';
|
||||||
@@ -25,6 +29,60 @@ const invaderCityId = 991_101;
|
|||||||
const sourceEventId = 991_100;
|
const sourceEventId = 991_100;
|
||||||
const createdEventIds = [991_101, 991_102];
|
const createdEventIds = [991_101, 991_102];
|
||||||
|
|
||||||
|
type ReferenceInvaderLifecycleTrace = {
|
||||||
|
phases: {
|
||||||
|
afterRaise: {
|
||||||
|
createdNationCount: number;
|
||||||
|
generalCountsPerNation: number[];
|
||||||
|
diplomacyCountsPerNation: number[];
|
||||||
|
diplomacyStates: string[];
|
||||||
|
isunited: number;
|
||||||
|
blockChangeScout: boolean;
|
||||||
|
};
|
||||||
|
atWar: { results: string[]; autoDeleteEventCount: number };
|
||||||
|
atPeace: {
|
||||||
|
results: string[];
|
||||||
|
autoDeleteEventCount: number;
|
||||||
|
rulerWanderTurnCount: number;
|
||||||
|
endingEventPresent: boolean;
|
||||||
|
};
|
||||||
|
afterUserWin: {
|
||||||
|
result: string;
|
||||||
|
endingEventPresent: boolean;
|
||||||
|
isunited: number;
|
||||||
|
refreshLimit: number;
|
||||||
|
logs: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const runReferenceInvaderLifecycle = (): ReferenceInvaderLifecycleTrace | null => {
|
||||||
|
const workspaceRoot = process.env.TURN_DIFFERENTIAL_WORKSPACE_ROOT;
|
||||||
|
if (process.env.TURN_DIFFERENTIAL_REFERENCE !== '1' || !workspaceRoot) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const stackDirectory = path.join(workspaceRoot, 'docker_compose_files/reference');
|
||||||
|
const runnerScript =
|
||||||
|
process.env.MONTHLY_INVADER_LIFECYCLE_RUNNER_SCRIPT ??
|
||||||
|
path.join(workspaceRoot, 'ref/sam/hwe/compare/monthly_event_trace.php');
|
||||||
|
const fixturePath =
|
||||||
|
process.env.MONTHLY_INVADER_LIFECYCLE_FIXTURE ??
|
||||||
|
path.join(path.dirname(runnerScript), 'fixtures/monthly_invader_lifecycle.json');
|
||||||
|
const stdout = execFileSync('./scripts/run-turn-differential-case.sh', ['-'], {
|
||||||
|
cwd: stackDirectory,
|
||||||
|
input: fs.readFileSync(fixturePath, 'utf8'),
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
COMPOSE_PROJECT_NAME: 'sam-rebuild-reference',
|
||||||
|
TURN_DIFFERENTIAL_RUNNER_SCRIPT: runnerScript,
|
||||||
|
TURN_DIFFERENTIAL_COMPARE_DIR: path.dirname(runnerScript),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return JSON.parse(stdout) as ReferenceInvaderLifecycleTrace;
|
||||||
|
};
|
||||||
|
|
||||||
const buildCity = (id: number, nationId: number, level: number, name: string): City => ({
|
const buildCity = (id: number, nationId: number, level: number, name: string): City => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -155,6 +213,7 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('commits invader entities, dynamic events, diplomacy, turns, ranks, city state, and logs atomically', async () => {
|
it('commits invader entities, dynamic events, diplomacy, turns, ranks, city state, and logs atomically', async () => {
|
||||||
|
const referenceTrace = runReferenceInvaderLifecycle();
|
||||||
const createdGeneralIds = Array.from({ length: 10 }, (_, index) => firstCreatedGeneralId + index);
|
const createdGeneralIds = Array.from({ length: 10 }, (_, index) => firstCreatedGeneralId + index);
|
||||||
const createCity = (city: City) =>
|
const createCity = (city: City) =>
|
||||||
db.city.create({
|
db.city.create({
|
||||||
@@ -386,6 +445,15 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
expect(await db.worldState.findUniqueOrThrow({ where: { id: stateRow.id } })).toMatchObject({
|
expect(await db.worldState.findUniqueOrThrow({ where: { id: stateRow.id } })).toMatchObject({
|
||||||
meta: expect.objectContaining({ isunited: 1, block_change_scout: false }),
|
meta: expect.objectContaining({ isunited: 1, block_change_scout: false }),
|
||||||
});
|
});
|
||||||
|
if (referenceTrace) {
|
||||||
|
expect(referenceTrace.phases.afterRaise).toMatchObject({
|
||||||
|
generalCountsPerNation: [10],
|
||||||
|
diplomacyCountsPerNation: [2],
|
||||||
|
diplomacyStates: ['1:24'],
|
||||||
|
isunited: 1,
|
||||||
|
blockChangeScout: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
const sequenceProbe = await db.event.create({
|
const sequenceProbe = await db.event.create({
|
||||||
data: {
|
data: {
|
||||||
targetCode: 'month',
|
targetCode: 'month',
|
||||||
@@ -399,6 +467,23 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
await db.event.delete({ where: { id: sequenceProbe.id } });
|
await db.event.delete({ where: { id: sequenceProbe.id } });
|
||||||
|
|
||||||
expect(world.removeEvent(sourceEventId)).toBe(true);
|
expect(world.removeEvent(sourceEventId)).toBe(true);
|
||||||
|
await world.advanceMonth(new Date('0200-02-01T00:00:00.000Z'));
|
||||||
|
await dbHooks.hooks.flushChanges?.({
|
||||||
|
lastTurnTime: world.getState().lastTurnTime.toISOString(),
|
||||||
|
processedGenerals: 0,
|
||||||
|
processedTurns: 1,
|
||||||
|
durationMs: 0,
|
||||||
|
partial: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await db.event.findUnique({ where: { id: createdEventIds[0]! } })).not.toBeNull();
|
||||||
|
if (referenceTrace) {
|
||||||
|
expect(referenceTrace.phases.atWar).toEqual({
|
||||||
|
results: ['On War'],
|
||||||
|
autoDeleteEventCount: referenceTrace.phases.afterRaise.createdNationCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
world.applyDiplomacyPatch({
|
world.applyDiplomacyPatch({
|
||||||
srcNationId: createdNationId,
|
srcNationId: createdNationId,
|
||||||
destNationId: existingNationId,
|
destNationId: existingNationId,
|
||||||
@@ -409,7 +494,7 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
destNationId: createdNationId,
|
destNationId: createdNationId,
|
||||||
patch: { state: 2, term: 0 },
|
patch: { state: 2, term: 0 },
|
||||||
});
|
});
|
||||||
await world.advanceMonth(new Date('0200-02-01T00:00:00.000Z'));
|
await world.advanceMonth(new Date('0200-03-01T00:00:00.000Z'));
|
||||||
await dbHooks.hooks.flushChanges?.({
|
await dbHooks.hooks.flushChanges?.({
|
||||||
lastTurnTime: world.getState().lastTurnTime.toISOString(),
|
lastTurnTime: world.getState().lastTurnTime.toISOString(),
|
||||||
processedGenerals: 0,
|
processedGenerals: 0,
|
||||||
@@ -426,13 +511,24 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
})
|
})
|
||||||
).toEqual(Array.from({ length: 30 }, () => ({ actionCode: 'che_방랑' })));
|
).toEqual(Array.from({ length: 30 }, () => ({ actionCode: 'che_방랑' })));
|
||||||
expect(await db.event.findUnique({ where: { id: createdEventIds[1]! } })).not.toBeNull();
|
expect(await db.event.findUnique({ where: { id: createdEventIds[1]! } })).not.toBeNull();
|
||||||
|
if (referenceTrace) {
|
||||||
|
expect(referenceTrace.phases.atPeace).toMatchObject({
|
||||||
|
results: ['Deleted'],
|
||||||
|
autoDeleteEventCount: 0,
|
||||||
|
endingEventPresent: true,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
referenceTrace.phases.atPeace.rulerWanderTurnCount /
|
||||||
|
referenceTrace.phases.afterRaise.createdNationCount
|
||||||
|
).toBe(30);
|
||||||
|
}
|
||||||
|
|
||||||
for (const generalId of createdGeneralIds) {
|
for (const generalId of createdGeneralIds) {
|
||||||
expect(world.removeGeneral(generalId)).toBe(true);
|
expect(world.removeGeneral(generalId)).toBe(true);
|
||||||
}
|
}
|
||||||
expect(world.removeNation(createdNationId)).toBe(true);
|
expect(world.removeNation(createdNationId)).toBe(true);
|
||||||
world.updateCity(invaderCityId, { nationId: existingNationId });
|
world.updateCity(invaderCityId, { nationId: existingNationId });
|
||||||
await world.advanceMonth(new Date('0200-03-01T00:00:00.000Z'));
|
await world.advanceMonth(new Date('0200-04-01T00:00:00.000Z'));
|
||||||
await dbHooks.hooks.flushChanges?.({
|
await dbHooks.hooks.flushChanges?.({
|
||||||
lastTurnTime: world.getState().lastTurnTime.toISOString(),
|
lastTurnTime: world.getState().lastTurnTime.toISOString(),
|
||||||
processedGenerals: 0,
|
processedGenerals: 0,
|
||||||
@@ -447,14 +543,26 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await db.logEntry.findMany({
|
await db.logEntry.findMany({
|
||||||
where: { year: 200, month: 3, text: { contains: '【이벤트】' } },
|
where: { year: 200, month: 4, text: { contains: '【이벤트】' } },
|
||||||
orderBy: { id: 'asc' },
|
orderBy: { id: 'asc' },
|
||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
).toEqual([
|
).toEqual([
|
||||||
{ text: '<R>★</>200년 3월:<L><b>【이벤트】</b></>이민족을 모두 소탕했습니다!' },
|
{ text: '<C>●</>200년 4월:<L><b>【이벤트】</b></>이민족을 모두 소탕했습니다!' },
|
||||||
{ text: '<R>★</>200년 3월:<L><b>【이벤트】</b></>중원은 당분간 태평성대를 누릴 것입니다.' },
|
{ text: '<C>●</>200년 4월:<L><b>【이벤트】</b></>중원은 당분간 태평성대를 누릴 것입니다.' },
|
||||||
]);
|
]);
|
||||||
|
if (referenceTrace) {
|
||||||
|
expect(referenceTrace.phases.afterUserWin).toEqual({
|
||||||
|
result: 'Deleted',
|
||||||
|
endingEventPresent: false,
|
||||||
|
isunited: 3,
|
||||||
|
refreshLimit: 300,
|
||||||
|
logs: [
|
||||||
|
'<C>●</>200년 4월:<L><b>【이벤트】</b></>이민족을 모두 소탕했습니다!',
|
||||||
|
'<C>●</>200년 4월:<L><b>【이벤트】</b></>중원은 당분간 태평성대를 누릴 것입니다.',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await dbHooks.close();
|
await dbHooks.close();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user