fix(parity): preserve legacy monthly turn ordering
This commit is contained in:
@@ -36,6 +36,7 @@ export interface ActionContextWorldState {
|
||||
currentMonth: number;
|
||||
tickSeconds: number;
|
||||
lastTurnTime?: Date;
|
||||
lastTurnTick?: number;
|
||||
meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RandomGenerator } from '@sammo-ts/common';
|
||||
import { GAME_TICKS_PER_TURN, JosaUtil, type RandomGenerator } from '@sammo-ts/common';
|
||||
import type {
|
||||
City,
|
||||
General,
|
||||
@@ -19,7 +19,6 @@ import type {
|
||||
import { createGeneralAddEffect } from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||
import { buildRecruitmentGeneral } from './recruitment.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
import { buildWorldSummary } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js';
|
||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||
@@ -59,6 +58,8 @@ export interface TalentScoutResolveContext<
|
||||
createGeneralId: () => number;
|
||||
turnTermMinutes: number;
|
||||
turnTimeBase: Date;
|
||||
turnTimeBaseTick?: number;
|
||||
ticksPerSecond: number;
|
||||
}
|
||||
|
||||
export interface TalentScoutEnvironment {
|
||||
@@ -439,6 +440,12 @@ export class ActionResolver<
|
||||
const cityId = resolveSpawnCityId(context, context.rng, this.env);
|
||||
const turnSecond = randomRangeInt(context.rng, 0, context.turnTermMinutes * 60 - 1);
|
||||
const turnFraction = randomRangeInt(context.rng, 0, 999_999);
|
||||
const turnTick =
|
||||
context.turnTimeBaseTick === undefined
|
||||
? undefined
|
||||
: context.turnTimeBaseTick +
|
||||
turnSecond * context.ticksPerSecond +
|
||||
Math.floor((turnFraction * context.ticksPerSecond) / 1_000_000);
|
||||
const turnTime = new Date(
|
||||
context.turnTimeBase.getTime() + turnSecond * 1_000 + Math.floor(turnFraction / 1_000)
|
||||
);
|
||||
@@ -489,6 +496,7 @@ export class ActionResolver<
|
||||
meta,
|
||||
}),
|
||||
turnTime,
|
||||
...(turnTick === undefined ? {} : { turnTick }),
|
||||
bornYear: birthYear,
|
||||
deadYear: deathYear,
|
||||
affinity,
|
||||
@@ -602,6 +610,8 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => ({
|
||||
// GeneralBuilder::build() derives a new NPC turn from gameStor.turntime,
|
||||
// not from the scout's own reserved-turn timestamp.
|
||||
turnTimeBase: options.world.lastTurnTime ?? base.general.turnTime,
|
||||
turnTimeBaseTick: options.world.lastTurnTick,
|
||||
ticksPerSecond: GAME_TICKS_PER_TURN / options.world.tickSeconds,
|
||||
});
|
||||
|
||||
export const commandSpec: GeneralTurnCommandSpec = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RandomGenerator } from '@sammo-ts/common';
|
||||
import { GAME_TICKS_PER_TURN, JosaUtil, type RandomGenerator } from '@sammo-ts/common';
|
||||
import type {
|
||||
General,
|
||||
GeneralMeta,
|
||||
@@ -25,7 +25,6 @@ import type {
|
||||
import { createGeneralAddEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||
import { buildRecruitmentGeneral } from '@sammo-ts/logic/actions/turn/general/recruitment.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
import {
|
||||
buildAverageNationGeneralCount,
|
||||
@@ -64,6 +63,8 @@ export interface VolunteerRecruitResolveContext<
|
||||
createGeneralId: () => number;
|
||||
turnTermSeconds: number;
|
||||
turnTimeBase: Date;
|
||||
turnTimeBaseTick?: number;
|
||||
ticksPerSecond: number;
|
||||
}
|
||||
|
||||
export interface VolunteerRecruitEnvironment {
|
||||
@@ -398,6 +399,12 @@ export class ActionResolver<
|
||||
candidate.personality ?? legacyChoice(context.rng, this.env.availablePersonalities ?? ['che_안전']);
|
||||
const turnSecond = randomRangeInt(context.rng, 0, context.turnTermSeconds - 1);
|
||||
const turnFraction = randomRangeInt(context.rng, 0, 999_999);
|
||||
const turnTick =
|
||||
context.turnTimeBaseTick === undefined
|
||||
? undefined
|
||||
: context.turnTimeBaseTick +
|
||||
turnSecond * context.ticksPerSecond +
|
||||
Math.floor((turnFraction * context.ticksPerSecond) / 1_000_000);
|
||||
const turnTime = new Date(
|
||||
context.turnTimeBase.getTime() + turnSecond * 1_000 + Math.floor(turnFraction / 1_000)
|
||||
);
|
||||
@@ -444,6 +451,7 @@ export class ActionResolver<
|
||||
meta,
|
||||
}),
|
||||
turnTime,
|
||||
...(turnTick === undefined ? {} : { turnTick }),
|
||||
};
|
||||
effects.push(createGeneralAddEffect(newGeneral));
|
||||
}
|
||||
@@ -520,6 +528,8 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
||||
createGeneralId: options.createGeneralId,
|
||||
turnTermSeconds: Math.max(1, Math.round(options.world.tickSeconds)),
|
||||
turnTimeBase: options.world.lastTurnTime ?? base.general.turnTime,
|
||||
turnTimeBaseTick: options.world.lastTurnTick,
|
||||
ticksPerSecond: GAME_TICKS_PER_TURN / options.world.tickSeconds,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -143,20 +143,35 @@ const canonicalizeWarTrait = (raw: string | null | undefined): string | null =>
|
||||
};
|
||||
|
||||
// Scenario rows expose one legacy speciality column. GeneralBuilder tries the
|
||||
// domestic catalogue first, then the war catalogue, and persists the resolved
|
||||
// class code. Preserve unknown values in the domestic slot so custom scenario
|
||||
// packs retain their prior data even when their module is not installed here.
|
||||
// war catalogue first, then the domestic catalogue, and persists the resolved
|
||||
// class code. An explicit war slot keeps the single speciality in the domestic
|
||||
// slot. Preserve unknown values in the domestic slot so custom scenario packs
|
||||
// retain their prior data even when their module is not installed here.
|
||||
const resolveScenarioTraits = (
|
||||
special: string | null,
|
||||
explicitWar: string | null | undefined
|
||||
): { specialDomestic: string | null; specialWar: string | null } => {
|
||||
const domestic = canonicalizeDomesticTrait(special);
|
||||
const inferredWar = domestic === null ? canonicalizeWarTrait(special) : null;
|
||||
const inferredWar = canonicalizeWarTrait(special);
|
||||
const retainedDomestic = special && special !== 'None' ? special : null;
|
||||
const retainedWar = explicitWar && explicitWar !== 'None' ? explicitWar : null;
|
||||
const resolvedExplicitWar = canonicalizeWarTrait(explicitWar) ?? retainedWar;
|
||||
|
||||
if (resolvedExplicitWar !== null) {
|
||||
return {
|
||||
specialDomestic: domestic ?? retainedDomestic,
|
||||
specialWar: resolvedExplicitWar,
|
||||
};
|
||||
}
|
||||
if (inferredWar !== null) {
|
||||
return {
|
||||
specialDomestic: null,
|
||||
specialWar: inferredWar,
|
||||
};
|
||||
}
|
||||
return {
|
||||
specialDomestic: domestic ?? (inferredWar === null ? retainedDomestic : null),
|
||||
specialWar: canonicalizeWarTrait(explicitWar) ?? inferredWar ?? retainedWar,
|
||||
specialDomestic: domestic ?? retainedDomestic,
|
||||
specialWar: null,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -192,17 +192,17 @@ describe('scenario bootstrap', () => {
|
||||
expect(result.snapshot.generals[0]?.role.specialWar).toBeNull();
|
||||
expect(result.snapshot.generals[1]?.role).toMatchObject({
|
||||
personality: 'che_출세',
|
||||
specialDomestic: 'che_event_의술',
|
||||
specialWar: null,
|
||||
specialDomestic: null,
|
||||
specialWar: 'che_의술',
|
||||
});
|
||||
expect(result.seed.generals[1]).toMatchObject({
|
||||
special: 'che_event_의술',
|
||||
specialWar: null,
|
||||
special: null,
|
||||
specialWar: 'che_의술',
|
||||
});
|
||||
expect(result.snapshot.generals[2]?.role).toMatchObject({
|
||||
personality: 'che_패권',
|
||||
specialDomestic: 'che_event_돌격',
|
||||
specialWar: null,
|
||||
specialDomestic: null,
|
||||
specialWar: 'che_돌격',
|
||||
});
|
||||
expect(result.snapshot.generals[0]?.meta).toMatchObject({
|
||||
explevel: 0,
|
||||
|
||||
Reference in New Issue
Block a user