Merge branch 'main' into feature/nation-seizure-npc-message-parity
This commit is contained in:
@@ -13,6 +13,7 @@ export interface CanonicalTurnSnapshot {
|
||||
engine: CanonicalEngine;
|
||||
world: Record<string, unknown>;
|
||||
generals: Array<Record<string, unknown>>;
|
||||
rankData: Array<Record<string, unknown>>;
|
||||
cities: Array<Record<string, unknown>>;
|
||||
nations: Array<Record<string, unknown>>;
|
||||
diplomacy: Array<Record<string, unknown>>;
|
||||
@@ -91,6 +92,7 @@ export const projectCoreDatabaseSnapshot = (rows: {
|
||||
meta: unknown;
|
||||
};
|
||||
generals: Array<Record<string, unknown>>;
|
||||
rankData: Array<Record<string, unknown>>;
|
||||
cities: Array<Record<string, unknown>>;
|
||||
nations: Array<Record<string, unknown>>;
|
||||
diplomacy: Array<Record<string, unknown>>;
|
||||
@@ -99,6 +101,7 @@ export const projectCoreDatabaseSnapshot = (rows: {
|
||||
logs: Array<Record<string, unknown>>;
|
||||
}): CanonicalTurnSnapshot => {
|
||||
const worldMeta = asRecord(rows.world.meta);
|
||||
const legacyRankTypes = new Set<string>(LEGACY_RANK_DATA_TYPES);
|
||||
const generals = rows.generals.map((row) => {
|
||||
const meta = asRecord(row.meta);
|
||||
return {
|
||||
@@ -235,6 +238,14 @@ export const projectCoreDatabaseSnapshot = (rows: {
|
||||
isUnited: readNumber(worldMeta, 'isUnited', readNumber(worldMeta, 'isunited')),
|
||||
},
|
||||
generals,
|
||||
rankData: rows.rankData
|
||||
.filter((row) => typeof row.type === 'string' && legacyRankTypes.has(row.type))
|
||||
.map((row) => ({
|
||||
generalId: row.generalId,
|
||||
nationId: row.nationId,
|
||||
type: row.type,
|
||||
value: row.value,
|
||||
})),
|
||||
cities,
|
||||
nations,
|
||||
diplomacy,
|
||||
@@ -248,3 +259,4 @@ export const projectCoreDatabaseSnapshot = (rows: {
|
||||
},
|
||||
};
|
||||
};
|
||||
import { LEGACY_RANK_DATA_TYPES } from '@sammo-ts/common';
|
||||
|
||||
@@ -14,6 +14,12 @@ export interface SnapshotComparisonOptions {
|
||||
type FlatSnapshot = Map<string, unknown>;
|
||||
|
||||
const entityKey = (value: Record<string, unknown>, index: number): string => {
|
||||
if (
|
||||
(typeof value.generalId === 'number' || typeof value.generalId === 'string') &&
|
||||
typeof value.type === 'string'
|
||||
) {
|
||||
return `${String(value.generalId)}:${value.type}`;
|
||||
}
|
||||
for (const key of ['id', 'generalId', 'nationId', 'fromNationId']) {
|
||||
const candidate = value[key];
|
||||
if (typeof candidate === 'number' || typeof candidate === 'string') {
|
||||
|
||||
@@ -18,6 +18,10 @@ import type {
|
||||
TurnWorldSnapshot,
|
||||
TurnWorldState,
|
||||
} from '@sammo-ts/game-engine/turn/types.js';
|
||||
import {
|
||||
applyPersistedRankRowsToMeta,
|
||||
buildLegacyComparableRankRows,
|
||||
} from '@sammo-ts/game-engine/turn/rankData.js';
|
||||
|
||||
import {
|
||||
canonicalizeTurnCommandArgs,
|
||||
@@ -47,6 +51,7 @@ export interface TurnCommandFixtureRequest {
|
||||
};
|
||||
isolateWorld?: boolean;
|
||||
generals?: Array<Record<string, unknown>>;
|
||||
rankData?: Array<{ generalId: number; type: string; value: number }>;
|
||||
nations?: Array<Record<string, unknown>>;
|
||||
cities?: Array<Record<string, unknown>>;
|
||||
troops?: Array<Record<string, unknown>>;
|
||||
@@ -295,6 +300,17 @@ const buildWorldInput = (
|
||||
const month = readNumber(referenceBefore.world, 'month', request.setup?.world?.month ?? 1);
|
||||
const turnTime = new Date(`${String(year).padStart(4, '0')}-${String(month).padStart(2, '0')}-01T00:00:00.000Z`);
|
||||
const generals = referenceBefore.generals.map((row) => buildGeneral(row, turnTime));
|
||||
for (const general of generals) {
|
||||
applyPersistedRankRowsToMeta(
|
||||
general.meta,
|
||||
referenceBefore.rankData
|
||||
.filter((row) => readNumber(row, 'generalId') === general.id)
|
||||
.map((row) => ({
|
||||
type: readString(row, 'type', ''),
|
||||
value: readNumber(row, 'value'),
|
||||
}))
|
||||
);
|
||||
}
|
||||
const referenceGeneralCooldowns = Array.isArray(referenceBefore.world.generalCooldowns)
|
||||
? referenceBefore.world.generalCooldowns
|
||||
: [];
|
||||
@@ -524,6 +540,11 @@ const projectWorld = (
|
||||
}),
|
||||
},
|
||||
generals,
|
||||
rankData: world
|
||||
.listGenerals()
|
||||
.filter((general) => selector.generalIds.has(general.id))
|
||||
.flatMap(buildLegacyComparableRankRows)
|
||||
.map((row) => ({ ...row })),
|
||||
cities: world
|
||||
.listCities()
|
||||
.filter((city) => selector.cityIds.has(city.id))
|
||||
|
||||
@@ -11,11 +11,15 @@ export const readCoreDatabaseSnapshot = async (
|
||||
try {
|
||||
const db = connector.prisma;
|
||||
const world = await db.worldState.findFirstOrThrow({ orderBy: { id: 'asc' } });
|
||||
const [generals, cities, nations, diplomacy, generalTurns, nationTurns, logs] = await Promise.all([
|
||||
const [generals, rankData, cities, nations, diplomacy, generalTurns, nationTurns, logs] = await Promise.all([
|
||||
db.general.findMany({
|
||||
where: { id: { in: selector.generalIds } },
|
||||
orderBy: { id: 'asc' },
|
||||
}),
|
||||
db.rankData.findMany({
|
||||
where: { generalId: { in: selector.generalIds } },
|
||||
orderBy: [{ generalId: 'asc' }, { type: 'asc' }],
|
||||
}),
|
||||
db.city.findMany({
|
||||
where: { id: { in: selector.cityIds } },
|
||||
orderBy: { id: 'asc' },
|
||||
@@ -54,6 +58,7 @@ export const readCoreDatabaseSnapshot = async (
|
||||
return projectCoreDatabaseSnapshot({
|
||||
world,
|
||||
generals,
|
||||
rankData,
|
||||
cities,
|
||||
nations,
|
||||
diplomacy,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { LEGACY_RANK_DATA_TYPES } from '@sammo-ts/common';
|
||||
|
||||
import { compareTurnSnapshotDeltas } from '../src/turn-differential/compare.js';
|
||||
import { runCoreTurnCommandTrace, type TurnCommandFixtureRequest } from '../src/turn-differential/coreCommandTrace.js';
|
||||
@@ -72,6 +73,7 @@ interface FixturePatches {
|
||||
troops?: Array<Record<string, unknown>>;
|
||||
diplomacy?: Record<string, Record<string, unknown>>;
|
||||
randomFoundingCandidateCityIds?: number[];
|
||||
rankData?: Array<{ generalId: number; type: string; value: number }>;
|
||||
}
|
||||
|
||||
const buildRequest = (
|
||||
@@ -166,6 +168,7 @@ const buildRequest = (
|
||||
{ ...general(2, 2, 70, 12), ...fixturePatches.generals?.[2] },
|
||||
{ ...general(3, 1, 3, 1), ...fixturePatches.generals?.[3] },
|
||||
],
|
||||
...(fixturePatches.rankData ? { rankData: fixturePatches.rankData } : {}),
|
||||
...(fixturePatches.troops ? { troops: fixturePatches.troops } : {}),
|
||||
...(fixturePatches.randomFoundingCandidateCityIds
|
||||
? { randomFoundingCandidateCityIds: fixturePatches.randomFoundingCandidateCityIds }
|
||||
@@ -382,6 +385,67 @@ integration('general command success matrix', () => {
|
||||
);
|
||||
});
|
||||
|
||||
integration('명장일람 rank_data command parity', () => {
|
||||
it('화계 increments firenum from the same seeded value as legacy', async () => {
|
||||
const request = buildRequest(
|
||||
'che_화계',
|
||||
{ destCityID: 70 },
|
||||
{ intelligence: 100 },
|
||||
{
|
||||
generals: { 2: { intelligence: 10 } },
|
||||
rankData: [{ generalId: 1, type: 'firenum', value: 17 }],
|
||||
}
|
||||
);
|
||||
request.setup!.world!.hiddenSeed = 'general-injury-4';
|
||||
const reference = runReferenceTurnCommandTraceRequest(
|
||||
workspaceRoot!,
|
||||
request as unknown as Record<string, unknown>
|
||||
);
|
||||
const core = await runCoreTurnCommandTrace(request, reference.before);
|
||||
|
||||
expect(reference.after.rankData).toContainEqual(
|
||||
expect.objectContaining({ generalId: 1, type: 'firenum', value: 18 })
|
||||
);
|
||||
expect(
|
||||
compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, {
|
||||
ignoredPathPatterns: ignoredLifecyclePaths,
|
||||
})
|
||||
).toEqual([]);
|
||||
}, 120_000);
|
||||
|
||||
it('은퇴 resets every legacy RankColumn row exactly like legacy', async () => {
|
||||
const request = buildRequest(
|
||||
'che_은퇴',
|
||||
undefined,
|
||||
{ age: 65, lastTurn: { command: '은퇴', term: 1 } },
|
||||
{
|
||||
rankData: LEGACY_RANK_DATA_TYPES.map((type, index) => ({
|
||||
generalId: 1,
|
||||
type,
|
||||
value: index + 1,
|
||||
})),
|
||||
}
|
||||
);
|
||||
const reference = runReferenceTurnCommandTraceRequest(
|
||||
workspaceRoot!,
|
||||
request as unknown as Record<string, unknown>
|
||||
);
|
||||
const core = await runCoreTurnCommandTrace(request, reference.before);
|
||||
|
||||
expect(reference.after.rankData.filter((row) => row.generalId === 1)).toHaveLength(
|
||||
LEGACY_RANK_DATA_TYPES.length
|
||||
);
|
||||
expect(reference.after.rankData.filter((row) => row.generalId === 1).every((row) => row.value === 0)).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, {
|
||||
ignoredPathPatterns: ignoredLifecyclePaths,
|
||||
})
|
||||
).toEqual([]);
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
type GeneralFailureCase = {
|
||||
action: string;
|
||||
args?: Record<string, unknown>;
|
||||
|
||||
@@ -15,6 +15,7 @@ const snapshot = (
|
||||
engine,
|
||||
world: { year: 183, month: 1, tickMinutes: 10, turnTime: '0183-01-01T00:00:00.000Z', isUnited: 0 },
|
||||
generals: [{ id: 1, gold: 1000, rice: 1000, crew: 1000, nationId: 1, cityId: 1 }],
|
||||
rankData: [],
|
||||
cities: [{ id: 1, nationId: 1, agriculture: 1000, defence: 500 }],
|
||||
nations: [{ id: 1, gold: 0, rice: 0 }],
|
||||
diplomacy: [],
|
||||
@@ -44,6 +45,23 @@ describe('turn snapshot differential comparator', () => {
|
||||
expect(compareTurnSnapshots(reference, core)).toEqual([]);
|
||||
});
|
||||
|
||||
it('compares rank rows by general and type instead of array position', () => {
|
||||
const reference = snapshot('ref', {
|
||||
rankData: [
|
||||
{ generalId: 2, nationId: 1, type: 'firenum', value: 3 },
|
||||
{ generalId: 1, nationId: 1, type: 'warnum', value: 5 },
|
||||
],
|
||||
});
|
||||
const core = snapshot('core2026', {
|
||||
rankData: [
|
||||
{ generalId: 1, nationId: 1, type: 'warnum', value: 5 },
|
||||
{ generalId: 2, nationId: 1, type: 'firenum', value: 3 },
|
||||
],
|
||||
});
|
||||
|
||||
expect(compareTurnSnapshots(reference, core)).toEqual([]);
|
||||
});
|
||||
|
||||
it('normalizes legacy ID argument spelling at the trace boundary', () => {
|
||||
expect(
|
||||
canonicalizeTurnCommandArgs({
|
||||
|
||||
Reference in New Issue
Block a user