fix: complete reserved nation command parity
This commit is contained in:
@@ -62,9 +62,9 @@ normalized to the core `*Id` spelling before command identity comparison.
|
|||||||
The matrix includes the four-call `전투태세` path, lifecycle and
|
The matrix includes the four-call `전투태세` path, lifecycle and
|
||||||
appointment commands, troop assembly, rebellion and all three founding
|
appointment commands, troop assembly, rebellion and all three founding
|
||||||
variants.
|
variants.
|
||||||
- `turnCommandNationMatrix.integration.test.ts`: 32 successful nation
|
- `turnCommandNationMatrix.integration.test.ts`: all 35 reserved nation
|
||||||
command paths, including all nine unit-research commands and the
|
command paths, including the generated-general state and 36-call RNG
|
||||||
multi-turn `필사즉생`, `허보`, and `초토화` paths.
|
trace of `의병모집`.
|
||||||
- `turnCommandCoreReference.integration.test.ts`: declaration and live
|
- `turnCommandCoreReference.integration.test.ts`: declaration and live
|
||||||
sortie fixtures.
|
sortie fixtures.
|
||||||
|
|
||||||
@@ -169,11 +169,11 @@ Compatibility is established per case only when:
|
|||||||
4. live sortie also passes the battle trace comparison;
|
4. live sortie also passes the battle trace comparison;
|
||||||
5. any ignored path is documented in the case evidence.
|
5. any ignored path is documented in the case evidence.
|
||||||
|
|
||||||
As of 2026-07-26, 54 general matrix cases, 32 nation matrix cases, declaration
|
As of 2026-07-26, 54 general matrix cases, 35 nation matrix cases, declaration
|
||||||
and live sortie pass this boundary. Live sortie is the remaining general
|
and live sortie pass this boundary. Live sortie is the remaining general
|
||||||
command key and covers battle entry, conquest, defeated-general neutralization
|
command key and covers battle entry, conquest, defeated-general neutralization
|
||||||
and last-city nation collapse. Thus all 55 general command keys have a
|
and last-city nation collapse. Thus all 55 general command keys have a
|
||||||
completed success-path comparison. This is 88 executable comparison cases;
|
completed success-path comparison. This is 91 executable comparison cases;
|
||||||
it is not yet a claim that failure/boundary paths or all 38 nation commands
|
it is not yet a claim that failure/boundary paths or all 38 nation commands
|
||||||
have been dynamically compared.
|
have been dynamically compared.
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface ActionContextWorldState {
|
|||||||
currentYear: number;
|
currentYear: number;
|
||||||
currentMonth: number;
|
currentMonth: number;
|
||||||
tickSeconds: number;
|
tickSeconds: number;
|
||||||
|
lastTurnTime?: Date;
|
||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface NationSummary {
|
|||||||
averageStats?: General['stats'];
|
averageStats?: General['stats'];
|
||||||
averageExperience?: number;
|
averageExperience?: number;
|
||||||
averageDedication?: number;
|
averageDedication?: number;
|
||||||
|
averageDex?: [number, number, number, number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildWorldSummary = (world: ActionContextWorldRef | null): WorldSummary => {
|
export const buildWorldSummary = (world: ActionContextWorldRef | null): WorldSummary => {
|
||||||
@@ -68,6 +69,16 @@ export const buildNationSummary = (world: ActionContextWorldRef | null, nationId
|
|||||||
);
|
);
|
||||||
const expSum = generals.reduce((acc, general) => acc + general.experience, 0);
|
const expSum = generals.reduce((acc, general) => acc + general.experience, 0);
|
||||||
const dedSum = generals.reduce((acc, general) => acc + general.dedication, 0);
|
const dedSum = generals.reduce((acc, general) => acc + general.dedication, 0);
|
||||||
|
const dexSum = generals.reduce<[number, number, number, number, number]>(
|
||||||
|
(acc, general) => [
|
||||||
|
acc[0] + Number(general.meta.dex1 ?? 0),
|
||||||
|
acc[1] + Number(general.meta.dex2 ?? 0),
|
||||||
|
acc[2] + Number(general.meta.dex3 ?? 0),
|
||||||
|
acc[3] + Number(general.meta.dex4 ?? 0),
|
||||||
|
acc[4] + Number(general.meta.dex5 ?? 0),
|
||||||
|
],
|
||||||
|
[0, 0, 0, 0, 0] as [number, number, number, number, number]
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
averageStats: {
|
averageStats: {
|
||||||
leadership: statSum.leadership / total,
|
leadership: statSum.leadership / total,
|
||||||
@@ -76,6 +87,7 @@ export const buildNationSummary = (world: ActionContextWorldRef | null, nationId
|
|||||||
},
|
},
|
||||||
averageExperience: expSum / total,
|
averageExperience: expSum / total,
|
||||||
averageDedication: dedSum / total,
|
averageDedication: dedSum / total,
|
||||||
|
averageDex: [dexSum[0] / total, dexSum[1] / total, dexSum[2] / total, dexSum[3] / total, dexSum[4] / total],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,11 @@ export interface VolunteerRecruitResolveContext<
|
|||||||
nationAverageStats?: StatBlock;
|
nationAverageStats?: StatBlock;
|
||||||
nationAverageExperience?: number;
|
nationAverageExperience?: number;
|
||||||
nationAverageDedication?: number;
|
nationAverageDedication?: number;
|
||||||
|
nationAverageDex?: [number, number, number, number, number];
|
||||||
generalPool?: VolunteerRecruitCandidate[];
|
generalPool?: VolunteerRecruitCandidate[];
|
||||||
createGeneralId: () => number;
|
createGeneralId: () => number;
|
||||||
|
turnTermSeconds: number;
|
||||||
|
turnTimeBase: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VolunteerRecruitEnvironment {
|
export interface VolunteerRecruitEnvironment {
|
||||||
@@ -71,6 +74,13 @@ export interface VolunteerRecruitEnvironment {
|
|||||||
npcDeathYears?: number;
|
npcDeathYears?: number;
|
||||||
killTurnMin?: number;
|
killTurnMin?: number;
|
||||||
killTurnMax?: number;
|
killTurnMax?: number;
|
||||||
|
npcStatTotal?: number;
|
||||||
|
npcStatMin?: number;
|
||||||
|
npcStatMax?: number;
|
||||||
|
randomGeneralFirstNames?: string[];
|
||||||
|
randomGeneralMiddleNames?: string[];
|
||||||
|
randomGeneralLastNames?: string[];
|
||||||
|
availablePersonalities?: string[];
|
||||||
decorateName?: (name: string, npcState: number) => string;
|
decorateName?: (name: string, npcState: number) => string;
|
||||||
pickCandidate?: (context: VolunteerRecruitResolveContext, rng: RandomGenerator) => VolunteerRecruitCandidate | null;
|
pickCandidate?: (context: VolunteerRecruitResolveContext, rng: RandomGenerator) => VolunteerRecruitCandidate | null;
|
||||||
buildStats?: (
|
buildStats?: (
|
||||||
@@ -91,20 +101,9 @@ const DEFAULT_NPC_DEATH_YEARS = 10;
|
|||||||
const DEFAULT_KILLTURN_MIN = 64;
|
const DEFAULT_KILLTURN_MIN = 64;
|
||||||
const DEFAULT_KILLTURN_MAX = 70;
|
const DEFAULT_KILLTURN_MAX = 70;
|
||||||
const DEFAULT_SPEC_AGE = 19;
|
const DEFAULT_SPEC_AGE = 19;
|
||||||
|
const DEFAULT_NPC_STAT_TOTAL = 150;
|
||||||
const resolveKillturnFromDeathYear = (
|
const DEFAULT_NPC_STAT_MIN = 10;
|
||||||
currentYear: number,
|
const DEFAULT_NPC_STAT_MAX = 75;
|
||||||
currentMonth: number,
|
|
||||||
deathYear: number,
|
|
||||||
rng: RandomGenerator
|
|
||||||
): number => {
|
|
||||||
if (!Number.isFinite(deathYear) || deathYear <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const deathMonth = randomRangeInt(rng, 1, 12);
|
|
||||||
const diff = (deathYear - currentYear) * 12 + (deathMonth - currentMonth);
|
|
||||||
return Math.max(diff, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addMetaValue = (
|
const addMetaValue = (
|
||||||
meta: Record<string, TriggerValue>,
|
meta: Record<string, TriggerValue>,
|
||||||
@@ -124,6 +123,58 @@ const readMetaNumber = (meta: Record<string, TriggerValue>, key: string): number
|
|||||||
|
|
||||||
const randomRangeInt = (rng: RandomGenerator, min: number, max: number): number => rng.nextInt(min, max + 1);
|
const randomRangeInt = (rng: RandomGenerator, min: number, max: number): number => rng.nextInt(min, max + 1);
|
||||||
|
|
||||||
|
type InclusiveRandomGenerator = RandomGenerator & {
|
||||||
|
nextIntInclusive?: (maxInclusive: number) => number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyChoiceIndex = (rng: RandomGenerator, length: number): number => {
|
||||||
|
if (length <= 0) {
|
||||||
|
throw new Error('Empty items');
|
||||||
|
}
|
||||||
|
const inclusive = rng as InclusiveRandomGenerator;
|
||||||
|
return inclusive.nextIntInclusive ? inclusive.nextIntInclusive(length - 1) : rng.nextInt(0, length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyChoice = <T>(rng: RandomGenerator, values: readonly T[]): T =>
|
||||||
|
values[legacyChoiceIndex(rng, values.length)]!;
|
||||||
|
|
||||||
|
const pickByWeight = <T extends string>(rng: RandomGenerator, weights: Record<T, number>): T => {
|
||||||
|
const entries = Object.entries(weights) as Array<[T, number]>;
|
||||||
|
const first = entries[0];
|
||||||
|
if (!first) {
|
||||||
|
throw new Error('Empty weights');
|
||||||
|
}
|
||||||
|
const total = entries.reduce((sum, [, weight]) => sum + Math.max(0, weight), 0);
|
||||||
|
let cursor = rng.nextFloat1() * total;
|
||||||
|
for (const [key, weight] of entries) {
|
||||||
|
cursor -= Math.max(0, weight);
|
||||||
|
if (cursor <= 0) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries.at(-1)?.[0] ?? first[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildLegacyRandomStats = (
|
||||||
|
rng: RandomGenerator,
|
||||||
|
env: VolunteerRecruitEnvironment
|
||||||
|
): { stats: StatBlock; pickType: '무' | '지' } => {
|
||||||
|
const total = env.npcStatTotal ?? DEFAULT_NPC_STAT_TOTAL;
|
||||||
|
const min = env.npcStatMin ?? DEFAULT_NPC_STAT_MIN;
|
||||||
|
const max = env.npcStatMax ?? DEFAULT_NPC_STAT_MAX;
|
||||||
|
const pickType = pickByWeight(rng, { 무: 5, 지: 5 });
|
||||||
|
const main = max - randomRangeInt(rng, 0, min);
|
||||||
|
const other = min + randomRangeInt(rng, 0, Math.trunc(min / 2));
|
||||||
|
const sub = total - main - other;
|
||||||
|
return {
|
||||||
|
pickType,
|
||||||
|
stats:
|
||||||
|
pickType === '무'
|
||||||
|
? { leadership: sub, strength: main, intelligence: other }
|
||||||
|
: { leadership: sub, strength: other, intelligence: main },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const resolveRelYear = (ctx: ConstraintContext): number => {
|
const resolveRelYear = (ctx: ConstraintContext): number => {
|
||||||
const relYear = ctx.env.relYear;
|
const relYear = ctx.env.relYear;
|
||||||
if (typeof relYear === 'number') {
|
if (typeof relYear === 'number') {
|
||||||
@@ -281,53 +332,92 @@ export class ActionResolver<
|
|||||||
const deathYears = this.env.npcDeathYears ?? DEFAULT_NPC_DEATH_YEARS;
|
const deathYears = this.env.npcDeathYears ?? DEFAULT_NPC_DEATH_YEARS;
|
||||||
const killTurnMin = this.env.killTurnMin ?? DEFAULT_KILLTURN_MIN;
|
const killTurnMin = this.env.killTurnMin ?? DEFAULT_KILLTURN_MIN;
|
||||||
const killTurnMax = this.env.killTurnMax ?? DEFAULT_KILLTURN_MAX;
|
const killTurnMax = this.env.killTurnMax ?? DEFAULT_KILLTURN_MAX;
|
||||||
|
const firstNames = this.env.randomGeneralFirstNames ?? ['가'];
|
||||||
|
const middleNames = this.env.randomGeneralMiddleNames ?? [''];
|
||||||
|
const lastNames = this.env.randomGeneralLastNames ?? ['가'];
|
||||||
|
const candidates = Array.from({ length: createCount }, () => {
|
||||||
|
const selected = resolveCandidate(context, context.rng, this.env);
|
||||||
|
if (selected) {
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: `${legacyChoice(context.rng, firstNames)}${legacyChoice(
|
||||||
|
context.rng,
|
||||||
|
middleNames
|
||||||
|
)}${legacyChoice(context.rng, lastNames)}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
for (let idx = 0; idx < createCount; idx += 1) {
|
for (const candidate of candidates) {
|
||||||
const newGeneralId = context.createGeneralId();
|
const newGeneralId = context.createGeneralId();
|
||||||
const candidate = resolveCandidate(context, context.rng, this.env) ?? { name: `NPC_${newGeneralId}` };
|
const name = this.env.decorateName ? this.env.decorateName(candidate.name, NPC_TYPE) : `ⓖ${candidate.name}`;
|
||||||
const name = this.env.decorateName ? this.env.decorateName(candidate.name, NPC_TYPE) : candidate.name;
|
|
||||||
const birthYear = context.currentYear - baseAge;
|
const birthYear = context.currentYear - baseAge;
|
||||||
const deathYear = context.currentYear + deathYears;
|
const deathYear = context.currentYear + deathYears;
|
||||||
const stats = resolveStats(context, context.rng, this.env, candidate);
|
const killturn = randomRangeInt(context.rng, killTurnMin, killTurnMax);
|
||||||
|
const affinity = candidate.affinity ?? randomRangeInt(context.rng, 1, 150);
|
||||||
|
const generated = buildLegacyRandomStats(context.rng, this.env);
|
||||||
|
const stats = candidate.stats ? resolveStats(context, context.rng, this.env, candidate) : generated.stats;
|
||||||
|
const averageDex = context.nationAverageDex ?? [0, 0, 0, 0, 0];
|
||||||
|
const dexTotal = averageDex[0] + averageDex[1] + averageDex[2] + averageDex[3];
|
||||||
|
const dex: [number, number, number, number] =
|
||||||
|
generated.pickType === '무'
|
||||||
|
? legacyChoice(context.rng, [
|
||||||
|
[(dexTotal * 5) / 8, dexTotal / 8, dexTotal / 8, dexTotal / 8],
|
||||||
|
[dexTotal / 8, (dexTotal * 5) / 8, dexTotal / 8, dexTotal / 8],
|
||||||
|
[dexTotal / 8, dexTotal / 8, (dexTotal * 5) / 8, dexTotal / 8],
|
||||||
|
])
|
||||||
|
: [dexTotal / 8, dexTotal / 8, dexTotal / 8, (dexTotal * 5) / 8];
|
||||||
|
const personality =
|
||||||
|
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 turnTime = new Date(
|
||||||
|
context.turnTimeBase.getTime() + turnSecond * 1_000 + Math.floor(turnFraction / 1_000)
|
||||||
|
);
|
||||||
const meta: GeneralMeta = {
|
const meta: GeneralMeta = {
|
||||||
killturn: resolveKillturnFromDeathYear(
|
killturn,
|
||||||
context.currentYear,
|
|
||||||
context.currentMonth,
|
|
||||||
deathYear,
|
|
||||||
context.rng
|
|
||||||
),
|
|
||||||
npcType: NPC_TYPE,
|
npcType: NPC_TYPE,
|
||||||
crewTypeId: this.env.defaultCrewTypeId,
|
crewTypeId: this.env.defaultCrewTypeId,
|
||||||
|
dex1: dex[0],
|
||||||
|
dex2: dex[1],
|
||||||
|
dex3: dex[2],
|
||||||
|
dex4: dex[3],
|
||||||
|
dex5: averageDex[4],
|
||||||
|
turnSecond,
|
||||||
|
turnFraction,
|
||||||
};
|
};
|
||||||
addMetaValue(meta, 'affinity', candidate.affinity ?? null);
|
addMetaValue(meta, 'affinity', affinity);
|
||||||
addMetaValue(meta, 'picture', candidate.picture ?? null);
|
addMetaValue(meta, 'picture', candidate.picture ?? null);
|
||||||
addMetaValue(meta, 'birthYear', birthYear);
|
addMetaValue(meta, 'birthYear', birthYear);
|
||||||
|
addMetaValue(meta, 'deathYear', deathYear);
|
||||||
addMetaValue(meta, 'specAge', DEFAULT_SPEC_AGE);
|
addMetaValue(meta, 'specAge', DEFAULT_SPEC_AGE);
|
||||||
addMetaValue(meta, 'specAge2', DEFAULT_SPEC_AGE);
|
addMetaValue(meta, 'specAge2', DEFAULT_SPEC_AGE);
|
||||||
addMetaValue(meta, 'killturn', meta.killturn || randomRangeInt(context.rng, killTurnMin, killTurnMax));
|
|
||||||
addMetaValue(meta, 'text', candidate.text ?? null);
|
addMetaValue(meta, 'text', candidate.text ?? null);
|
||||||
|
|
||||||
const newGeneral = buildRecruitmentGeneral<TriggerState>({
|
const newGeneral = {
|
||||||
id: newGeneralId,
|
...buildRecruitmentGeneral<TriggerState>({
|
||||||
name,
|
id: newGeneralId,
|
||||||
nationId: context.general.nationId,
|
name,
|
||||||
cityId: context.general.cityId,
|
nationId: context.general.nationId,
|
||||||
stats,
|
cityId: context.general.cityId,
|
||||||
officerLevel: 1,
|
stats,
|
||||||
age: baseAge,
|
officerLevel: 1,
|
||||||
npcState: NPC_TYPE,
|
age: baseAge,
|
||||||
gold: this.env.defaultNpcGold,
|
npcState: NPC_TYPE,
|
||||||
rice: this.env.defaultNpcRice,
|
gold: this.env.defaultNpcGold,
|
||||||
experience: context.nationAverageExperience ?? 0,
|
rice: this.env.defaultNpcRice,
|
||||||
dedication: context.nationAverageDedication ?? 0,
|
experience: context.nationAverageExperience ?? 0,
|
||||||
crewTypeId: this.env.defaultCrewTypeId,
|
dedication: context.nationAverageDedication ?? 0,
|
||||||
role: {
|
crewTypeId: this.env.defaultCrewTypeId,
|
||||||
personality: candidate.personality ?? null,
|
role: {
|
||||||
specialDomestic: this.env.defaultSpecialDomestic,
|
personality,
|
||||||
specialWar: this.env.defaultSpecialWar,
|
specialDomestic: this.env.defaultSpecialDomestic,
|
||||||
},
|
specialWar: this.env.defaultSpecialWar,
|
||||||
meta,
|
},
|
||||||
});
|
meta,
|
||||||
|
}),
|
||||||
|
turnTime,
|
||||||
|
};
|
||||||
effects.push(createGeneralAddEffect(newGeneral));
|
effects.push(createGeneralAddEffect(newGeneral));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,7 +486,10 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
|||||||
nationAverageStats: nationSummary.averageStats,
|
nationAverageStats: nationSummary.averageStats,
|
||||||
nationAverageExperience: nationSummary.averageExperience,
|
nationAverageExperience: nationSummary.averageExperience,
|
||||||
nationAverageDedication: nationSummary.averageDedication,
|
nationAverageDedication: nationSummary.averageDedication,
|
||||||
|
nationAverageDex: nationSummary.averageDex,
|
||||||
createGeneralId: options.createGeneralId,
|
createGeneralId: options.createGeneralId,
|
||||||
|
turnTermSeconds: Math.max(1, Math.round(options.world.tickSeconds)),
|
||||||
|
turnTimeBase: options.world.lastTurnTime ?? base.general.turnTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export interface CounterStrategyResolveContext<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ACTION_NAME = '피장파장';
|
const ACTION_NAME = '피장파장';
|
||||||
const DEFAULT_GLOBAL_DELAY = 8;
|
|
||||||
const TARGET_DELAY = 60;
|
const TARGET_DELAY = 60;
|
||||||
const PRE_REQ_TURN = 1;
|
const PRE_REQ_TURN = 1;
|
||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
@@ -201,10 +200,8 @@ export class ActionResolver<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nation) {
|
if (nation) {
|
||||||
const globalDelay = DEFAULT_GLOBAL_DELAY;
|
|
||||||
nation.meta = {
|
nation.meta = {
|
||||||
...(nation.meta as object),
|
...(nation.meta as object),
|
||||||
strategic_cmd_limit: globalDelay,
|
|
||||||
[`next_execute_${targetCommandName}`]: currentYearMonth + this.getTargetPostReqTurn(context),
|
[`next_execute_${targetCommandName}`]: currentYearMonth + this.getTargetPostReqTurn(context),
|
||||||
};
|
};
|
||||||
effects.push(
|
effects.push(
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ describe('Nation Missing Actions', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(resolution.general.experience).toBeGreaterThan(100);
|
expect(resolution.general.experience).toBeGreaterThan(100);
|
||||||
expect(resolution.nation?.meta?.strategic_cmd_limit).toBe(8);
|
expect(resolution.nation?.meta?.strategic_cmd_limit).toBe(0);
|
||||||
expect(resolution.nation?.meta?.next_execute_허보).toBe(227);
|
expect(resolution.nation?.meta?.next_execute_허보).toBe(227);
|
||||||
expect(resolution.patches?.nations).toContainEqual({
|
expect(resolution.patches?.nations).toContainEqual({
|
||||||
id: destNation.id,
|
id: destNation.id,
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ const buildWorldInput = (
|
|||||||
}));
|
}));
|
||||||
const snapshot: TurnWorldSnapshot = {
|
const snapshot: TurnWorldSnapshot = {
|
||||||
scenarioConfig: {
|
scenarioConfig: {
|
||||||
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 70 },
|
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 75, npcMin: 10, chiefMin: 70 },
|
||||||
iconPath: '',
|
iconPath: '',
|
||||||
map: {},
|
map: {},
|
||||||
const: {
|
const: {
|
||||||
@@ -586,7 +586,10 @@ export const runCoreTurnCommandTrace = async (
|
|||||||
const worldInput = buildWorldInput(request, referenceBefore, unitSet, map);
|
const worldInput = buildWorldInput(request, referenceBefore, unitSet, map);
|
||||||
const { state, snapshot } = worldInput;
|
const { state, snapshot } = worldInput;
|
||||||
const selector = {
|
const selector = {
|
||||||
generalIds: new Set(referenceBefore.generals.map((row) => readNumber(row, 'id'))),
|
generalIds: new Set([
|
||||||
|
...referenceBefore.generals.map((row) => readNumber(row, 'id')),
|
||||||
|
...(request.observe?.generalIds ?? []),
|
||||||
|
]),
|
||||||
cityIds: new Set(referenceBefore.cities.map((row) => readNumber(row, 'id'))),
|
cityIds: new Set(referenceBefore.cities.map((row) => readNumber(row, 'id'))),
|
||||||
nationIds: new Set(referenceBefore.nations.map((row) => readNumber(row, 'id'))),
|
nationIds: new Set(referenceBefore.nations.map((row) => readNumber(row, 'id'))),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ const buildRequest = (
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
observe: {
|
observe: {
|
||||||
generalIds: [1, 2, 3],
|
generalIds: [1, 2, 3, 4, 5, 6],
|
||||||
cityIds: [3, 70],
|
cityIds: [3, 70],
|
||||||
nationIds: [1, 2],
|
nationIds: [1, 2],
|
||||||
logAfterId: 0,
|
logAfterId: 0,
|
||||||
@@ -364,6 +364,61 @@ const cases: NationMatrixCase[] = [
|
|||||||
cities: { 70: { nationId: 1, supplyState: 1 } },
|
cities: { 70: { nationId: 1, supplyState: 1 } },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'che_의병모집',
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
nations: {
|
||||||
|
1: {
|
||||||
|
turnLastByOfficerLevel: {
|
||||||
|
12: { command: '의병모집', term: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'che_수몰',
|
||||||
|
{ destCityID: 70 },
|
||||||
|
{
|
||||||
|
nations: {
|
||||||
|
1: {
|
||||||
|
turnLastByOfficerLevel: {
|
||||||
|
12: { command: '수몰', arg: { destCityID: 70 }, term: 2 },
|
||||||
|
},
|
||||||
|
coreTurnLastByOfficerLevel: {
|
||||||
|
12: { command: '수몰', arg: { destCityId: 70 }, term: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
diplomacy: { '1:2': { state: 0 }, '2:1': { state: 0 } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'che_피장파장',
|
||||||
|
{ destNationID: 2, commandType: 'che_필사즉생' },
|
||||||
|
{
|
||||||
|
nations: {
|
||||||
|
1: {
|
||||||
|
turnLastByOfficerLevel: {
|
||||||
|
12: {
|
||||||
|
command: '피장파장',
|
||||||
|
arg: { destNationID: 2, commandType: 'che_필사즉생' },
|
||||||
|
term: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coreTurnLastByOfficerLevel: {
|
||||||
|
12: {
|
||||||
|
command: '피장파장',
|
||||||
|
arg: { destNationId: 2, commandType: 'che_필사즉생' },
|
||||||
|
term: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
diplomacy: { '1:2': { state: 0 }, '2:1': { state: 0 } },
|
||||||
|
},
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
integration('nation command success matrix', () => {
|
integration('nation command success matrix', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user