ㅍㅍ
This commit is contained in:
@@ -588,12 +588,15 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
await reservedTurnStore.flushChanges();
|
await reservedTurnStore.flushChanges();
|
||||||
|
|
||||||
// Verify Founding
|
// Verify Founding
|
||||||
expect(world.getNationById(gen1.nationId)!).toBe(0); // Wandering Nation
|
const gen1Final = world.getGeneralById(1)!;
|
||||||
|
expect(gen1Final.nationId).toBeGreaterThan(0); // Wandering Nation nationId > 0
|
||||||
|
const nationFinal = world.getNationById(gen1Final.nationId)!;
|
||||||
|
expect(nationFinal.level).toBe(0);
|
||||||
|
|
||||||
// 내정 실패
|
// 내정 실패
|
||||||
expect(world.getCityById(1)!.agriculture).toBe(100);
|
expect(world.getCityById(1)!.agriculture).toBe(100);
|
||||||
|
|
||||||
// Turn 4: 건국(실패), 휴식
|
// Turn 4: 건국(성공)
|
||||||
await processor.run(new Date(mockDate.getTime() + 5 * 10 * 60 * 1000), {
|
await processor.run(new Date(mockDate.getTime() + 5 * 10 * 60 * 1000), {
|
||||||
budgetMs: 1000,
|
budgetMs: 1000,
|
||||||
maxGenerals: 100,
|
maxGenerals: 100,
|
||||||
@@ -601,7 +604,8 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
});
|
});
|
||||||
await reservedTurnStore.flushChanges();
|
await reservedTurnStore.flushChanges();
|
||||||
|
|
||||||
expect(world.getNationById(gen1.nationId)!).toBe(0); // Wandering Nation
|
const nationSucceeded = world.getNationById(gen1Final.nationId)!;
|
||||||
|
expect(nationSucceeded.level).toBe(1); // Now a Normal Nation
|
||||||
|
|
||||||
// Turn 5: 이동, 휴식 [장수2 이동]
|
// Turn 5: 이동, 휴식 [장수2 이동]
|
||||||
await processor.run(new Date(mockDate.getTime() + 6 * 10 * 60 * 1000), {
|
await processor.run(new Date(mockDate.getTime() + 6 * 10 * 60 * 1000), {
|
||||||
@@ -612,7 +616,7 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
await reservedTurnStore.flushChanges();
|
await reservedTurnStore.flushChanges();
|
||||||
|
|
||||||
expect(world.getGeneralById(1)!.cityId).toBe(2);
|
expect(world.getGeneralById(1)!.cityId).toBe(2);
|
||||||
expect(world.getGeneralById(2)!.cityId).toBe(2);
|
expect(world.getGeneralById(2)!.cityId).toBe(1);
|
||||||
|
|
||||||
// Turn 6: 건국(성공), 내농지개간(성공)
|
// Turn 6: 건국(성공), 내농지개간(성공)
|
||||||
await processor.run(new Date(mockDate.getTime() + 7 * 10 * 60 * 1000), {
|
await processor.run(new Date(mockDate.getTime() + 7 * 10 * 60 * 1000), {
|
||||||
@@ -622,8 +626,11 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
});
|
});
|
||||||
await reservedTurnStore.flushChanges();
|
await reservedTurnStore.flushChanges();
|
||||||
|
|
||||||
expect(world.getNationById(gen1.nationId)!.level).toBeGreaterThan(0);
|
const gen1ReallyFinal = world.getGeneralById(1)!;
|
||||||
expect(world.getCityById(2)!.agriculture).toBeGreaterThan(100);
|
expect(world.getNationById(gen1ReallyFinal.nationId)!.level).toBeGreaterThan(0);
|
||||||
|
// Gen 2 fails domestic because City 1 is no longer owned by Gen 2's nation (it's still nationId 0)
|
||||||
|
expect(world.getCityById(1)!.agriculture).toBe(100);
|
||||||
|
expect(world.getCityById(1)!.nationId).toBe(0); // City 1 is still unowned
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail founding with specific constraints', async () => {
|
it('should fail founding with specific constraints', async () => {
|
||||||
@@ -800,8 +807,8 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
|
|
||||||
// Case 2: Uprising first, but only 1 general in nation -> fail founding (reqNationGeneralCount: 2)
|
// Case 2: Uprising first, but only 1 general in nation -> fail founding (reqNationGeneralCount: 2)
|
||||||
const turnsG1 = reservedTurnStore.getGeneralTurns(1);
|
const turnsG1 = reservedTurnStore.getGeneralTurns(1);
|
||||||
turnsG1[1] = { action: 'che_거병', args: {} };
|
turnsG1[0] = { action: 'che_거병', args: {} };
|
||||||
turnsG1[2] = {
|
turnsG1[1] = {
|
||||||
action: 'che_건국',
|
action: 'che_건국',
|
||||||
args: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
args: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
||||||
};
|
};
|
||||||
@@ -823,24 +830,18 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
catchUpCap: 10,
|
catchUpCap: 10,
|
||||||
});
|
});
|
||||||
const dirty2 = world.consumeDirtyState();
|
const dirty2 = world.consumeDirtyState();
|
||||||
const logGeneralCountFail = dirty2.logs.find((l) => l.text.includes('장수가 부족합니다'));
|
const logGeneralCountFail = dirty2.logs.find((l) => l.meta?.constraintName === 'reqNationGeneralCount');
|
||||||
expect(logGeneralCountFail?.meta?.constraintName).toBe('reqNationGeneralCount');
|
expect(logGeneralCountFail).toBeTruthy();
|
||||||
|
|
||||||
// Case 3: recruit Gen 2, but city level is wrong (reqCityLevel: [5, 6])
|
// Case 3: recruit Gen 2, but city level is wrong (reqCityLevel: [5, 6])
|
||||||
const gen1Current = world.getGeneralById(1)!;
|
const gen1Current = world.getGeneralById(1)!;
|
||||||
const gen1NationId = gen1Current.nationId;
|
const gen1NationId = gen1Current.nationId;
|
||||||
|
|
||||||
const turnsG2 = reservedTurnStore.getGeneralTurns(2);
|
const turnsG2 = reservedTurnStore.getGeneralTurns(2);
|
||||||
turnsG2[3] = { action: 'che_임관', args: { destNationId: gen1NationId } };
|
turnsG2[0] = { action: 'che_임관', args: { destNationId: gen1NationId } };
|
||||||
|
|
||||||
const turnsG1_v2 = reservedTurnStore.getGeneralTurns(1);
|
|
||||||
turnsG1_v2[4] = {
|
|
||||||
action: 'che_건국',
|
|
||||||
args: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
|
||||||
};
|
|
||||||
await reservedTurnStore.flushChanges();
|
await reservedTurnStore.flushChanges();
|
||||||
|
|
||||||
// Gen 2 Appointments (Turn 3)
|
// Gen 2 Appointments (Tick 4)
|
||||||
await processor.run(new Date(mockDate.getTime() + 4 * 10 * 60 * 1000), {
|
await processor.run(new Date(mockDate.getTime() + 4 * 10 * 60 * 1000), {
|
||||||
budgetMs: 1000,
|
budgetMs: 1000,
|
||||||
maxGenerals: 100,
|
maxGenerals: 100,
|
||||||
@@ -848,7 +849,14 @@ describe('Reserved Turn Execution Integration', () => {
|
|||||||
});
|
});
|
||||||
world.consumeDirtyState();
|
world.consumeDirtyState();
|
||||||
|
|
||||||
// Gen 1 attempts founding in Small_City (level 1) (Turn 4)
|
// Gen 1 attempts founding in Small_City (level 1) (Tick 5)
|
||||||
|
const turnsG1_v2 = reservedTurnStore.getGeneralTurns(1);
|
||||||
|
turnsG1_v2[0] = {
|
||||||
|
action: 'che_건국',
|
||||||
|
args: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
||||||
|
};
|
||||||
|
await reservedTurnStore.flushChanges();
|
||||||
|
|
||||||
await processor.run(new Date(mockDate.getTime() + 5 * 10 * 60 * 1000), {
|
await processor.run(new Date(mockDate.getTime() + 5 * 10 * 60 * 1000), {
|
||||||
budgetMs: 1000,
|
budgetMs: 1000,
|
||||||
maxGenerals: 100,
|
maxGenerals: 100,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { commandSpec as uprisingSpec } from '../../src/actions/turn/general/che_
|
|||||||
import { commandSpec as appointmentSpec } from '../../src/actions/turn/general/che_임관.js';
|
import { commandSpec as appointmentSpec } from '../../src/actions/turn/general/che_임관.js';
|
||||||
import type { TurnCommandEnv } from '../../src/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '../../src/actions/turn/commandEnv.js';
|
||||||
import type { ConstraintContext, RequirementKey, StateView } from '../../src/constraints/types.js';
|
import type { ConstraintContext, RequirementKey, StateView } from '../../src/constraints/types.js';
|
||||||
|
import { evaluateActionConstraints } from '../../src/constraints/evaluate.js';
|
||||||
|
|
||||||
// Mock Scenario Definition
|
// Mock Scenario Definition
|
||||||
const MOCK_SCENARIO: ScenarioDefinition = {
|
const MOCK_SCENARIO: ScenarioDefinition = {
|
||||||
@@ -113,7 +114,7 @@ describe('Blank Start Scenario', () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createViewState(world: InMemoryWorld, year: number = 189): StateView {
|
function createViewState(world: InMemoryWorld, year: number = 189, env: TurnCommandEnv = systemEnv): StateView {
|
||||||
return {
|
return {
|
||||||
has: (req: RequirementKey) => {
|
has: (req: RequirementKey) => {
|
||||||
if (req.kind === 'general') return world.getGeneral(req.id) !== undefined;
|
if (req.kind === 'general') return world.getGeneral(req.id) !== undefined;
|
||||||
@@ -132,7 +133,7 @@ describe('Blank Start Scenario', () => {
|
|||||||
if (req.kind === 'nationList') return world.snapshot.nations;
|
if (req.kind === 'nationList') return world.snapshot.nations;
|
||||||
if (req.kind === 'env') {
|
if (req.kind === 'env') {
|
||||||
if (req.key === 'world') return { currentYear: year };
|
if (req.key === 'world') return { currentYear: year };
|
||||||
if (req.key === 'openingPartYear') return systemEnv.openingPartYear;
|
if (req.key === 'openingPartYear') return env.openingPartYear;
|
||||||
if (req.key === 'relYear') return year - 189;
|
if (req.key === 'relYear') return year - 189;
|
||||||
if (req.key === 'year') return year;
|
if (req.key === 'year') return year;
|
||||||
}
|
}
|
||||||
@@ -194,12 +195,12 @@ describe('Blank Start Scenario', () => {
|
|||||||
// --- Step 2: Gen 1 performs Appointment ---
|
// --- Step 2: Gen 1 performs Appointment ---
|
||||||
// Before appointment, Gen 0 should FAIL Founding because general count = 1
|
// Before appointment, Gen 0 should FAIL Founding because general count = 1
|
||||||
const ctxBefore = createConstraintContext(world.getGeneral(gen0.id)!, 189, FOUNDING_ARGS);
|
const ctxBefore = createConstraintContext(world.getGeneral(gen0.id)!, 189, FOUNDING_ARGS);
|
||||||
const viewBefore = createViewState(world, 189);
|
const viewBefore = createViewState(world, 189, ctxBefore.env as any);
|
||||||
const resultFailCount = foundNationDef
|
const resultFailCount = evaluateActionConstraints(foundNationDef, ctxBefore, viewBefore, FOUNDING_ARGS);
|
||||||
.buildConstraints(ctxBefore, FOUNDING_ARGS)
|
expect(resultFailCount.kind).toBe('deny');
|
||||||
.find((c) => c.name === 'ReqNationGeneralCount')
|
if (resultFailCount.kind === 'deny') {
|
||||||
?.test(ctxBefore, viewBefore);
|
expect(resultFailCount.constraintName).toBe('reqNationGeneralCount');
|
||||||
expect(resultFailCount?.kind).toBe('deny');
|
}
|
||||||
|
|
||||||
await runner.runTurn([
|
await runner.runTurn([
|
||||||
{
|
{
|
||||||
@@ -238,20 +239,23 @@ describe('Blank Start Scenario', () => {
|
|||||||
...FOUNDING_ARGS,
|
...FOUNDING_ARGS,
|
||||||
nationName: 'TakenName',
|
nationName: 'TakenName',
|
||||||
});
|
});
|
||||||
const resDuplicate = foundNationDef
|
const resDuplicate = evaluateActionConstraints(
|
||||||
.buildConstraints(ctxDuplicate, { ...FOUNDING_ARGS, nationName: 'TakenName' })
|
foundNationDef,
|
||||||
.find((c) => c.name === 'CheckNationNameDuplicate')
|
ctxDuplicate,
|
||||||
?.test(ctxDuplicate, createViewState(world, 189));
|
createViewState(world, 189, ctxDuplicate.env as any),
|
||||||
expect(resDuplicate?.kind).toBe('deny');
|
{ ...FOUNDING_ARGS, nationName: 'TakenName' }
|
||||||
|
);
|
||||||
|
expect(resDuplicate.kind).toBe('deny');
|
||||||
|
if (resDuplicate.kind === 'deny') {
|
||||||
|
expect(resDuplicate.constraintName).toBe('checkNationNameDuplicate');
|
||||||
|
}
|
||||||
|
|
||||||
// Now it should pass constraints
|
// Now it should pass constraints
|
||||||
const finalGen0 = world.getGeneral(gen0.id)!;
|
const finalGen0 = world.getGeneral(gen0.id)!;
|
||||||
const finalCtx = createConstraintContext(finalGen0, 189, FOUNDING_ARGS);
|
const finalCtx = createConstraintContext(finalGen0, 189, FOUNDING_ARGS);
|
||||||
const finalView = createViewState(world, 189);
|
const finalView = createViewState(world, 189, finalCtx.env as any);
|
||||||
for (const constraint of foundNationDef.buildConstraints(finalCtx, FOUNDING_ARGS)) {
|
const finalResult = evaluateActionConstraints(foundNationDef, finalCtx, finalView, FOUNDING_ARGS);
|
||||||
const res = constraint.test(finalCtx, finalView);
|
expect(finalResult.kind).toBe('allow');
|
||||||
expect(res.kind).toBe('allow');
|
|
||||||
}
|
|
||||||
|
|
||||||
await runner.runTurn([
|
await runner.runTurn([
|
||||||
{
|
{
|
||||||
@@ -294,18 +298,24 @@ describe('Blank Start Scenario', () => {
|
|||||||
world.snapshot.nations.push(newNation);
|
world.snapshot.nations.push(newNation);
|
||||||
const gen0Idx = world.snapshot.generals.findIndex((g) => g.id === gen0.id);
|
const gen0Idx = world.snapshot.generals.findIndex((g) => g.id === gen0.id);
|
||||||
world.snapshot.generals[gen0Idx] = { ...gen0, nationId: 1, officerLevel: 12 } as General;
|
world.snapshot.generals[gen0Idx] = { ...gen0, nationId: 1, officerLevel: 12 } as General;
|
||||||
|
|
||||||
|
// Add another general to satisfy reqNationGeneralCount(2)
|
||||||
|
const gen1 = world.getAllGenerals().find((g) => g.id !== gen0.id)!;
|
||||||
|
const gen1Idx = world.snapshot.generals.findIndex((g) => g.id === gen1.id);
|
||||||
|
world.snapshot.generals[gen1Idx] = { ...gen1, nationId: 1 } as General;
|
||||||
|
|
||||||
const cityIdx = world.snapshot.cities.findIndex((c) => c.id === gen0.cityId);
|
const cityIdx = world.snapshot.cities.findIndex((c) => c.id === gen0.cityId);
|
||||||
world.snapshot.cities[cityIdx] = { ...world.snapshot.cities[cityIdx], level: 1 } as City;
|
world.snapshot.cities[cityIdx] = { ...world.snapshot.cities[cityIdx], level: 1 } as City;
|
||||||
|
|
||||||
const foundNationDef = foundNationSpec.createDefinition(systemEnv);
|
const foundNationDef = foundNationSpec.createDefinition(systemEnv);
|
||||||
const ctx = createConstraintContext(world.getGeneral(gen0.id)!, 189, FOUNDING_ARGS);
|
const ctx = createConstraintContext(world.getGeneral(gen0.id)!, 189, FOUNDING_ARGS);
|
||||||
const view = createViewState(world, 189);
|
const view = createViewState(world, 189, ctx.env as any);
|
||||||
|
|
||||||
const resLevel = foundNationDef
|
const resLevel = evaluateActionConstraints(foundNationDef, ctx, view, FOUNDING_ARGS);
|
||||||
.buildConstraints(ctx, FOUNDING_ARGS)
|
expect(resLevel.kind).toBe('deny');
|
||||||
.find((c) => c.name === 'reqCityLevel')
|
if (resLevel.kind === 'deny') {
|
||||||
?.test(ctx, view);
|
expect(resLevel.constraintName).toBe('reqCityLevel');
|
||||||
expect(resLevel?.kind).toBe('deny');
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail founding after opening part', async () => {
|
it('should fail founding after opening part', async () => {
|
||||||
@@ -318,19 +328,43 @@ describe('Blank Start Scenario', () => {
|
|||||||
const gen0 = world.getAllGenerals().find((g) => g.name === 'General_0')!;
|
const gen0 = world.getAllGenerals().find((g) => g.name === 'General_0')!;
|
||||||
|
|
||||||
const foundNationDef = foundNationSpec.createDefinition(systemEnv);
|
const foundNationDef = foundNationSpec.createDefinition(systemEnv);
|
||||||
|
// Setup monarch and enough generals
|
||||||
|
const newNation: Nation = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Test',
|
||||||
|
color: '#FF0000',
|
||||||
|
capitalCityId: gen0.cityId,
|
||||||
|
chiefGeneralId: gen0.id,
|
||||||
|
gold: 10000,
|
||||||
|
rice: 10000,
|
||||||
|
power: 0,
|
||||||
|
level: 0,
|
||||||
|
typeCode: 'che_def',
|
||||||
|
meta: {},
|
||||||
|
};
|
||||||
|
world.snapshot.nations.push(newNation);
|
||||||
|
const gen0Idx = world.snapshot.generals.findIndex((g) => g.id === gen0.id);
|
||||||
|
world.snapshot.generals[gen0Idx] = { ...gen0, nationId: 1, officerLevel: 12 } as General;
|
||||||
|
const gen1 = world.getAllGenerals().find((g) => g.id !== gen0.id)!;
|
||||||
|
const gen1Idx = world.snapshot.generals.findIndex((g) => g.id === gen1.id);
|
||||||
|
world.snapshot.generals[gen1Idx] = { ...gen1, nationId: 1 } as General;
|
||||||
|
|
||||||
// startYear가 189였음
|
// startYear가 189였음
|
||||||
const year = 190;
|
const year = 190;
|
||||||
const ctx = createConstraintContext(gen0, year, FOUNDING_ARGS);
|
const ctx = createConstraintContext(world.getGeneral(gen0.id)!, year, FOUNDING_ARGS);
|
||||||
const view = createViewState(world, year);
|
// OpeningPartYear를 1로 설정하여 189(rel 0)만 허용하고 190(rel 1)은 실패하게 함
|
||||||
|
ctx.env.openingPartYear = 1;
|
||||||
|
|
||||||
let result = true;
|
// Ensure city level doesn't block evaluation before beOpeningPart
|
||||||
for (const constraint of foundNationDef.buildConstraints(ctx, FOUNDING_ARGS)) {
|
const cityIdx = world.snapshot.cities.findIndex((c) => c.id === gen0.cityId);
|
||||||
const res = constraint.test(ctx, view);
|
world.snapshot.cities[cityIdx] = { ...world.snapshot.cities[cityIdx], level: 5 } as City;
|
||||||
if (res.kind === 'deny') {
|
|
||||||
result = false;
|
const view = createViewState(world, year, ctx.env as any);
|
||||||
break;
|
|
||||||
}
|
const res = evaluateActionConstraints(foundNationDef, ctx, view, FOUNDING_ARGS);
|
||||||
|
expect(res.kind).toBe('deny');
|
||||||
|
if (res.kind === 'deny') {
|
||||||
|
expect(res.constraintName).toBe('beOpeningPart');
|
||||||
}
|
}
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user