Merge branch 'main' into feature/nation-seizure-npc-message-parity

This commit is contained in:
2026-07-26 06:06:12 +00:00
39 changed files with 706 additions and 329 deletions
@@ -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,