feat: NPC 판단 당시 합성 정책을 기록하고 조회한다

This commit is contained in:
2026-09-16 09:21:50 +00:00
parent c9fe204c94
commit 292ca7662d
16 changed files with 216 additions and 40 deletions
+38 -1
View File
@@ -3,6 +3,39 @@ import { z } from 'zod';
import type { GamePrisma } from '@sammo-ts/infra';
import { auditProcedure, monthOrdinal, readAudit, readAuditWorld, zAuditMonth } from './shared.js';
const zEffectivePolicy = z.object({
schemaVersion: z.literal(1),
general: z.object({ priority: z.array(z.string()), flags: z.record(z.string(), z.boolean()) }),
nation: z.object({
priority: z.array(z.string()),
flags: z.record(z.string(), z.boolean()),
values: z.object({
reqNationGold: z.number(),
reqNationRice: z.number(),
reqHumanWarUrgentGold: z.number(),
reqHumanWarUrgentRice: z.number(),
reqHumanWarRecommandGold: z.number(),
reqHumanWarRecommandRice: z.number(),
reqHumanDevelGold: z.number(),
reqHumanDevelRice: z.number(),
reqNpcWarGold: z.number(),
reqNpcWarRice: z.number(),
reqNpcDevelGold: z.number(),
reqNpcDevelRice: z.number(),
minimumResourceActionAmount: z.number(),
maximumResourceActionAmount: z.number(),
minNpcWarLeadership: z.number(),
minWarCrew: z.number(),
minNpcRecruitCityPopulation: z.number(),
safeRecruitCityPopulationRatio: z.number(),
properWarTrainAtmos: z.number(),
cureThreshold: z.number(),
}),
combatForce: z.record(z.string(), z.array(z.number()).length(2)),
supportForce: z.array(z.number()),
developForce: z.array(z.number()),
}),
});
const zId = z.string().regex(/^[a-f0-9]{64}$/);
const zTick = z
.string()
@@ -71,7 +104,11 @@ const zStep = z.intersection(
)
.max(5),
}),
z.object({ kind: z.literal('DECISION_START'), reservedAction: z.string() }),
z.object({
kind: z.literal('DECISION_START'),
reservedAction: z.string(),
effectivePolicy: zEffectivePolicy.optional(),
}),
z.object({ kind: z.literal('DECISION_END'), action: z.string().nullable(), reason: z.string().nullable() }),
z.object({ kind: z.literal('DECISION_ERROR') }),
z.object({ kind: z.literal('PROCEDURE_START'), procedure: z.string() }),
@@ -2317,7 +2317,9 @@ integration('game API security over HTTP transport', () => {
{
decisionId: decisionIds[0]!,
ordinal: 0,
steps: Array.from({ length: 128 }, (_, sequence) => ({ ...step, sequence })),
steps: Array.from({ length: 128 }, (_, sequence) => sequence === 127
? { ...step, sequence, kind: 'DECISION_START', reservedAction: '휴식', effectivePolicy: {"schemaVersion":1,"general":{"priority":["징병"],"flags":{"징병":true,"출병":false}},"nation":{"priority":["천도"],"flags":{"천도":true},"values":{"reqNationGold":4321,"reqNationRice":100,"reqHumanWarUrgentGold":100,"reqHumanWarUrgentRice":100,"reqHumanWarRecommandGold":100,"reqHumanWarRecommandRice":100,"reqHumanDevelGold":100,"reqHumanDevelRice":100,"reqNpcWarGold":100,"reqNpcWarRice":100,"reqNpcDevelGold":100,"reqNpcDevelRice":100,"minimumResourceActionAmount":100,"maximumResourceActionAmount":100,"minNpcWarLeadership":100,"minWarCrew":100,"minNpcRecruitCityPopulation":100,"safeRecruitCityPopulationRatio":100,"properWarTrainAtmos":100,"cureThreshold":100},"combatForce":{"1":[2,3]},"supportForce":[4],"developForce":[5],"secret":"decision-secret"},"secret":"decision-secret"} }
: ({ ...step, sequence })),
},
{
decisionId: decisionIds[0]!,
@@ -2398,6 +2400,8 @@ integration('game API security over HTTP transport', () => {
},
});
expect(JSON.stringify(decisionPage.body)).not.toContain('decision-secret');
expect(JSON.stringify(decisionPage.body)).toContain('effectivePolicy');
expect(JSON.stringify(decisionPage.body)).toContain('4321');
expect((await get('decisionDetail', admin, { ...decisionDetailInput, cursor: 0 })).body).toMatchObject({
result: {
data: {