fix(engine): align monthly post tail ordering
This commit is contained in:
@@ -135,4 +135,60 @@ describe('neutral auction monthly registrar', () => {
|
||||
]);
|
||||
await registrar.close();
|
||||
});
|
||||
|
||||
it('continues after the legacy tournament roll and matches the tail-order fixture', async () => {
|
||||
const worldRef: { current: InMemoryTurnWorld | null } = { current: null };
|
||||
const now = new Date('2026-07-25T12:00:00.000Z');
|
||||
const registrar = await createNeutralAuctionRegistrar({
|
||||
databaseUrl: 'unused://test',
|
||||
profileName: 'test',
|
||||
getWorld: () => worldRef.current,
|
||||
getRedisClient: () => null,
|
||||
getWorldConfig: () => ({ tournamentTrig: true }),
|
||||
getNationPowerRollCount: () => 2,
|
||||
getTournamentRollConsumed: () => true,
|
||||
now: () => now,
|
||||
loadNeutralAuctionCounts: async () => [],
|
||||
});
|
||||
const snapshot = buildSnapshot();
|
||||
snapshot.nations = snapshot.nations.slice(0, 2);
|
||||
snapshot.generals = [buildGeneral(1, 0, 5_000, 7_000), buildGeneral(2, 0, 6_000, 8_000)];
|
||||
const state: TurnWorldState = {
|
||||
id: 1,
|
||||
currentYear: 193,
|
||||
currentMonth: 1,
|
||||
tickSeconds: 600,
|
||||
lastTurnTime: new Date('2026-07-25T00:00:00.000Z'),
|
||||
meta: { hiddenSeed: 'monthly-post-tail-2' },
|
||||
};
|
||||
const world = new InMemoryTurnWorld(state, snapshot, {
|
||||
schedule: { entries: [{ startMinute: 0, tickMinutes: 10 }] },
|
||||
calendarHandler: registrar.handler,
|
||||
});
|
||||
worldRef.current = world;
|
||||
|
||||
await world.advanceMonth(new Date('2026-07-25T00:10:00.000Z'));
|
||||
|
||||
expect(world.peekDirtyState().pendingNeutralAuctions).toEqual([
|
||||
expect.objectContaining({
|
||||
type: 'BUY_RICE',
|
||||
targetCode: '380',
|
||||
detail: expect.objectContaining({
|
||||
amount: 380,
|
||||
startBidAmount: 300,
|
||||
finishBidAmount: 750,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'SELL_RICE',
|
||||
targetCode: '830',
|
||||
detail: expect.objectContaining({
|
||||
amount: 830,
|
||||
startBidAmount: 990,
|
||||
finishBidAmount: 1_650,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
await registrar.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { RedisConnector } from '@sammo-ts/infra';
|
||||
import type { Nation } from '@sammo-ts/logic';
|
||||
|
||||
import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js';
|
||||
import { createTournamentAutoStartHandler } from '../src/turn/tournamentAutoStart.js';
|
||||
import type { TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js';
|
||||
|
||||
const buildNation = (id: number): Nation => ({
|
||||
id,
|
||||
name: `국가${id}`,
|
||||
color: '#777777',
|
||||
capitalCityId: null,
|
||||
chiefGeneralId: null,
|
||||
gold: 0,
|
||||
rice: 0,
|
||||
power: 0,
|
||||
level: 1,
|
||||
typeCode: 'che_중립',
|
||||
meta: {},
|
||||
});
|
||||
|
||||
describe('monthly tournament auto start', () => {
|
||||
it('uses the legacy monthly RNG after nation power rolls and persists the remaining pattern', async () => {
|
||||
const state: TurnWorldState = {
|
||||
id: 1,
|
||||
currentYear: 193,
|
||||
currentMonth: 1,
|
||||
tickSeconds: 600,
|
||||
lastTurnTime: new Date('0193-01-01T00:00:00.000Z'),
|
||||
meta: {
|
||||
hiddenSeed: 'monthly-post-tail-2',
|
||||
tournamentPattern: [0, 1, 2, 3],
|
||||
},
|
||||
};
|
||||
const snapshot: TurnWorldSnapshot = {
|
||||
scenarioConfig: {
|
||||
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 70 },
|
||||
iconPath: '',
|
||||
map: {},
|
||||
const: {},
|
||||
environment: { mapName: 'test', unitSet: 'default' },
|
||||
},
|
||||
map: { id: 'test', name: 'test', cities: [] },
|
||||
diplomacy: [],
|
||||
events: [],
|
||||
initialEvents: [],
|
||||
generals: [],
|
||||
cities: [],
|
||||
nations: [buildNation(1), buildNation(2)],
|
||||
troops: [],
|
||||
};
|
||||
const values = new Map<string, string>();
|
||||
const redis = {
|
||||
get: async (key: string) => values.get(key) ?? null,
|
||||
set: async (key: string, value: string) => {
|
||||
values.set(key, value);
|
||||
return 'OK';
|
||||
},
|
||||
} as unknown as RedisConnector['client'];
|
||||
let world: InMemoryTurnWorld | null = null;
|
||||
const consumed: boolean[] = [];
|
||||
world = new InMemoryTurnWorld(state, snapshot, {
|
||||
schedule: { entries: [{ startMinute: 0, tickMinutes: 10 }] },
|
||||
calendarHandler: createTournamentAutoStartHandler({
|
||||
profileName: 'test',
|
||||
getWorld: () => world,
|
||||
getRedisClient: () => redis,
|
||||
getWorldConfig: () => ({ tournamentTrig: true }),
|
||||
getNationPowerRollCount: () => 2,
|
||||
onTournamentRollConsumed: (value) => consumed.push(value),
|
||||
now: () => new Date('2026-07-25T00:00:00.000Z'),
|
||||
}),
|
||||
});
|
||||
|
||||
await world.advanceMonth(new Date('0193-02-01T00:00:00.000Z'));
|
||||
|
||||
expect(consumed).toEqual([false, true]);
|
||||
expect(JSON.parse(values.get('sammo:test:tournament:state') ?? '{}')).toMatchObject({
|
||||
stage: 1,
|
||||
phase: 0,
|
||||
type: 3,
|
||||
auto: true,
|
||||
openYear: 193,
|
||||
openMonth: 2,
|
||||
termSeconds: 600,
|
||||
nextAt: '2026-07-25T00:10:00.000Z',
|
||||
});
|
||||
expect(world.getState().meta.tournamentPattern).toEqual([0, 1, 2]);
|
||||
expect(world.peekDirtyState().logs).toEqual([
|
||||
expect.objectContaining({
|
||||
text: "<B><b>【대회】</b></><C>설전</> 대회가 개최됩니다! 천하의 <span class='ev_highlight'>책사</span>들을 모집하고 있습니다!",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not consume a tournament roll while a tournament is active', async () => {
|
||||
const state: TurnWorldState = {
|
||||
id: 1,
|
||||
currentYear: 193,
|
||||
currentMonth: 1,
|
||||
tickSeconds: 600,
|
||||
lastTurnTime: new Date('0193-01-01T00:00:00.000Z'),
|
||||
meta: { hiddenSeed: 'active-tournament' },
|
||||
};
|
||||
const snapshot: TurnWorldSnapshot = {
|
||||
scenarioConfig: {
|
||||
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 70 },
|
||||
iconPath: '',
|
||||
map: {},
|
||||
const: {},
|
||||
environment: { mapName: 'test', unitSet: 'default' },
|
||||
},
|
||||
map: { id: 'test', name: 'test', cities: [] },
|
||||
diplomacy: [],
|
||||
events: [],
|
||||
initialEvents: [],
|
||||
generals: [],
|
||||
cities: [],
|
||||
nations: [buildNation(1)],
|
||||
troops: [],
|
||||
};
|
||||
const redis = {
|
||||
get: async () => JSON.stringify({ stage: 1 }),
|
||||
set: async () => 'OK',
|
||||
} as unknown as RedisConnector['client'];
|
||||
let world: InMemoryTurnWorld | null = null;
|
||||
const consumed: boolean[] = [];
|
||||
world = new InMemoryTurnWorld(state, snapshot, {
|
||||
schedule: { entries: [{ startMinute: 0, tickMinutes: 10 }] },
|
||||
calendarHandler: createTournamentAutoStartHandler({
|
||||
profileName: 'test',
|
||||
getWorld: () => world,
|
||||
getRedisClient: () => redis,
|
||||
getWorldConfig: () => ({ tournamentTrig: true }),
|
||||
getNationPowerRollCount: () => 1,
|
||||
onTournamentRollConsumed: (value) => consumed.push(value),
|
||||
}),
|
||||
});
|
||||
|
||||
await world.advanceMonth(new Date('0193-02-01T00:00:00.000Z'));
|
||||
|
||||
expect(consumed).toEqual([false]);
|
||||
expect(world.peekDirtyState().logs).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user