constraint 실패 후 실패 검사 결과 항목을 직접 반환.

This commit is contained in:
2026-01-10 13:16:43 +00:00
parent d5d3949705
commit 3ec7e66948
9 changed files with 82 additions and 74 deletions
@@ -340,11 +340,12 @@ const buildConstraintContext = (
mode: 'full',
});
const createActionLog = (message: string): LogEntryDraft => ({
const createActionLog = (message: string, meta?: Record<string, unknown>): LogEntryDraft => ({
scope: LogScope.GENERAL,
category: LogCategory.ACTION,
format: LogFormat.MONTH,
text: message,
meta,
});
const resolveDefinition = (
@@ -505,7 +506,8 @@ export const createReservedTurnHandler = async (options: {
actionArgs = definition.parseArgs({}) ?? {};
actionKey = definition.key;
const reason = result.kind === 'deny' ? result.reason : '조건을 확인할 수 없습니다.';
logs.push(createActionLog(reason));
const meta = result.kind === 'deny' ? { constraintName: result.constraintName } : undefined;
logs.push(createActionLog(reason, meta));
}
const seedBase = buildSeedBase(context.world);
+30 -30
View File
@@ -14,7 +14,7 @@ import {
import type { Constraint, RequirementKey } from './types.js';
export const occupiedCity = (options: { allowNeutral?: boolean } = {}): Constraint => ({
name: 'OccupiedCity',
name: 'occupiedCity',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
if (ctx.cityId !== undefined) {
@@ -51,7 +51,7 @@ export const occupiedCity = (options: { allowNeutral?: boolean } = {}): Constrai
});
export const occupiedDestCity = (): Constraint => ({
name: 'OccupiedDestCity',
name: 'occupiedDestCity',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
const destCityId = resolveDestCityId(ctx);
@@ -90,7 +90,7 @@ export const occupiedDestCity = (): Constraint => ({
});
export const suppliedCity = (): Constraint => ({
name: 'SuppliedCity',
name: 'suppliedCity',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
@@ -109,15 +109,15 @@ export const suppliedCity = (): Constraint => ({
});
export const suppliedDestCity = (): Constraint => ({
name: 'SuppliedDestCity',
name: 'suppliedDestCity',
requires: (ctx) =>
resolveDestCityId(ctx) !== undefined
? [
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destCity = readDestCity(ctx, view);
@@ -140,7 +140,7 @@ export const suppliedDestCity = (): Constraint => ({
});
export const remainCityCapacity = (key: keyof City, label: string): Constraint => ({
name: 'RemainCityCapacity',
name: 'remainCityCapacity',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
@@ -165,7 +165,7 @@ export const remainCityCapacity = (key: keyof City, label: string): Constraint =
});
export const remainCityCapacityByMax = (key: keyof City, maxKey: keyof City, label: string): Constraint => ({
name: 'RemainCityCapacityByMax',
name: 'remainCityCapacityByMax',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
@@ -189,7 +189,7 @@ export const remainCityCapacityByMax = (key: keyof City, maxKey: keyof City, lab
});
export const reqCityCapacity = (key: keyof City, label: string, required: number | string): Constraint => ({
name: 'ReqCityCapacity',
name: 'reqCityCapacity',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
@@ -222,7 +222,7 @@ export const reqCityCapacity = (key: keyof City, label: string, required: number
});
export const reqCityTrust = (minTrust: number): Constraint => ({
name: 'ReqCityTrust',
name: 'reqCityTrust',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
@@ -245,15 +245,15 @@ export const reqCityTrust = (minTrust: number): Constraint => ({
});
export const existsDestCity = (): Constraint => ({
name: 'ExistsDestCity',
name: 'existsDestCity',
requires: (ctx) =>
resolveDestCityId(ctx) !== undefined
? [
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destCityId = resolveDestCityId(ctx);
@@ -273,7 +273,7 @@ export const existsDestCity = (): Constraint => ({
});
export const notOccupiedDestCity = (): Constraint => ({
name: 'NotOccupiedDestCity',
name: 'notOccupiedDestCity',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
const destCityId = resolveDestCityId(ctx);
@@ -311,15 +311,15 @@ export const notOccupiedDestCity = (): Constraint => ({
});
export const notNeutralDestCity = (): Constraint => ({
name: 'NotNeutralDestCity',
name: 'notNeutralDestCity',
requires: (ctx) =>
resolveDestCityId(ctx) !== undefined
? [
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destCity = readDestCity(ctx, view);
@@ -342,7 +342,7 @@ export const notNeutralDestCity = (): Constraint => ({
});
export const notSameDestCity = (): Constraint => ({
name: 'NotSameDestCity',
name: 'notSameDestCity',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
const destCityId = resolveDestCityId(ctx);
@@ -369,7 +369,7 @@ export const notSameDestCity = (): Constraint => ({
});
export const hasRouteWithEnemy = (): Constraint => ({
name: 'HasRouteWithEnemy',
name: 'hasRouteWithEnemy',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
const destCityId = resolveDestCityId(ctx);
@@ -435,7 +435,7 @@ export const hasRouteWithEnemy = (): Constraint => ({
});
export const beNeutralCity = (): Constraint => ({
name: 'BeNeutralCity',
name: 'beNeutralCity',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
@@ -463,7 +463,7 @@ export const beNeutralCity = (): Constraint => ({
});
export const reqCityLevel = (levels: number[]): Constraint => ({
name: 'ReqCityLevel',
name: 'reqCityLevel',
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
test: (ctx, view) => {
const city = readCity(view, ctx.cityId);
+6 -6
View File
@@ -32,8 +32,8 @@ const readDiplomacyEntry = (
typeof record.state === 'number'
? record.state
: typeof record.stateCode === 'number'
? record.stateCode
: null;
? record.stateCode
: null;
const term = typeof record.term === 'number' ? record.term : null;
return { state, term };
}
@@ -41,7 +41,7 @@ const readDiplomacyEntry = (
};
export const disallowDiplomacyBetweenStatus = (disallowList: Record<number, string>): Constraint => ({
name: 'DisallowDiplomacyBetweenStatus',
name: 'disallowDiplomacyBetweenStatus',
requires: (ctx) => {
const reqs: RequirementKey[] = [];
if (ctx.nationId !== undefined) {
@@ -93,7 +93,7 @@ export const disallowDiplomacyBetweenStatus = (disallowList: Record<number, stri
});
export const allowDiplomacyBetweenStatus = (allowList: number[], reason: string): Constraint => ({
name: 'AllowDiplomacyBetweenStatus',
name: 'allowDiplomacyBetweenStatus',
requires: (ctx) => {
const reqs: RequirementKey[] = [];
if (ctx.nationId !== undefined) {
@@ -144,7 +144,7 @@ export const allowDiplomacyBetweenStatus = (allowList: number[], reason: string)
});
export const allowDiplomacyWithTerm = (requiredState: number, minTerm: number, reason: string): Constraint => ({
name: 'AllowDiplomacyWithTerm',
name: 'allowDiplomacyWithTerm',
requires: (ctx) => {
const reqs: RequirementKey[] = [];
if (ctx.nationId !== undefined) {
@@ -195,7 +195,7 @@ export const allowDiplomacyWithTerm = (requiredState: number, minTerm: number, r
});
export const allowDiplomacyStatus = (allowList: number[], reason: string): Constraint => ({
name: 'AllowDiplomacyStatus',
name: 'allowDiplomacyStatus',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
if (ctx.nationId !== undefined) {
@@ -11,6 +11,12 @@ export const evaluateConstraints = (
return { kind: 'unknown', missing };
}
const result = constraint.test(ctx, view);
if (result.kind === 'deny') {
return {
...result,
constraintName: result.constraintName ?? constraint.name,
};
}
if (result.kind !== 'allow') {
return result;
}
+16 -16
View File
@@ -3,7 +3,7 @@ import { allow, readDestGeneral, resolveDestGeneralId, resolveDestNationId, unkn
import type { Constraint, ConstraintContext, RequirementKey, StateView } from './types.js';
export const notBeNeutral = (): Constraint => ({
name: 'NotBeNeutral',
name: 'notBeNeutral',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -22,7 +22,7 @@ export const notBeNeutral = (): Constraint => ({
});
export const beNeutral = (): Constraint => ({
name: 'BeNeutral',
name: 'beNeutral',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -41,7 +41,7 @@ export const beNeutral = (): Constraint => ({
});
export const beChief = (): Constraint => ({
name: 'BeChief',
name: 'beChief',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -60,7 +60,7 @@ export const beChief = (): Constraint => ({
});
export const beMonarch = (): Constraint => ({
name: 'BeMonarch',
name: 'beMonarch',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -82,7 +82,7 @@ export const reqGeneralGold = (
getRequiredGold: (ctx: ConstraintContext, view: StateView) => number,
requirements: RequirementKey[] = []
): Constraint => ({
name: 'ReqGeneralGold',
name: 'reqGeneralGold',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, ...requirements],
test: (ctx, view) => {
const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -106,7 +106,7 @@ export const reqGeneralRice = (
getRequiredRice: (ctx: ConstraintContext, view: StateView) => number,
requirements: RequirementKey[] = []
): Constraint => ({
name: 'ReqGeneralRice',
name: 'reqGeneralRice',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, ...requirements],
test: (ctx, view) => {
const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -127,7 +127,7 @@ export const reqGeneralRice = (
});
export const reqGeneralCrew = (): Constraint => ({
name: 'ReqGeneralCrew',
name: 'reqGeneralCrew',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -149,7 +149,7 @@ export const reqGeneralCrewMargin = (
getCrewTypeId: (ctx: ConstraintContext, view: StateView) => number | null,
requirements: RequirementKey[] = []
): Constraint => ({
name: 'ReqGeneralCrewMargin',
name: 'reqGeneralCrewMargin',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, ...requirements],
test: (ctx, view) => {
const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -177,15 +177,15 @@ export const reqGeneralCrewMargin = (
});
export const existsDestGeneral = (): Constraint => ({
name: 'ExistsDestGeneral',
name: 'existsDestGeneral',
requires: (ctx) =>
resolveDestGeneralId(ctx) !== undefined
? [
{
kind: 'destGeneral',
id: resolveDestGeneralId(ctx) ?? 0,
},
]
{
kind: 'destGeneral',
id: resolveDestGeneralId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destGeneralId = resolveDestGeneralId(ctx);
@@ -205,7 +205,7 @@ export const existsDestGeneral = (): Constraint => ({
});
export const destGeneralInDestNation = (): Constraint => ({
name: 'DestGeneralInDestNation',
name: 'destGeneralInDestNation',
requires: (ctx) => {
const reqs: RequirementKey[] = [];
const destGeneralId = resolveDestGeneralId(ctx);
@@ -246,7 +246,7 @@ export const destGeneralInDestNation = (): Constraint => ({
});
export const friendlyDestGeneral = (): Constraint => ({
name: 'FriendlyDestGeneral',
name: 'friendlyDestGeneral',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
const destGeneralId = resolveDestGeneralId(ctx);
+3 -3
View File
@@ -2,13 +2,13 @@ import { allow } from './helpers.js';
import type { Constraint } from './types.js';
export const alwaysFail = (reason: string): Constraint => ({
name: 'AlwaysFail',
name: 'alwaysFail',
requires: () => [],
test: () => ({ kind: 'deny', reason }),
});
export const notOpeningPart = (relYear: number, openingPartYear: number): Constraint => ({
name: 'NotOpeningPart',
name: 'notOpeningPart',
requires: () => [],
test: (_ctx) => {
if (relYear >= openingPartYear) {
@@ -19,7 +19,7 @@ export const notOpeningPart = (relYear: number, openingPartYear: number): Constr
});
export const beOpeningPart = (): Constraint => ({
name: 'BeOpeningPart',
name: 'beOpeningPart',
requires: () => [
{ kind: 'env', key: 'year' },
{ kind: 'env', key: 'openingPartYear' },
+14 -14
View File
@@ -3,7 +3,7 @@ import { allow, readGeneral, readMetaNumber, readNation, resolveDestNationId, un
import type { Constraint, ConstraintContext, RequirementKey, StateView } from './types.js';
export const notWanderingNation = (): Constraint => ({
name: 'NotWanderingNation',
name: 'notWanderingNation',
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
test: (ctx, view) => {
const nation = readNation(view, ctx.nationId);
@@ -22,7 +22,7 @@ export const notWanderingNation = (): Constraint => ({
});
export const beWanderingNation = (): Constraint => ({
name: 'BeWanderingNation',
name: 'beWanderingNation',
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
test: (ctx, view) => {
const nation = readNation(view, ctx.nationId);
@@ -41,7 +41,7 @@ export const beWanderingNation = (): Constraint => ({
});
export const availableStrategicCommand = (allowTurnCnt = 0): Constraint => ({
name: 'AvailableStrategicCommand',
name: 'availableStrategicCommand',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
if (ctx.nationId !== undefined) {
@@ -85,7 +85,7 @@ export const reqNationGold = (
getRequiredGold: (ctx: ConstraintContext, view: StateView) => number,
requirements: RequirementKey[] = []
): Constraint => ({
name: 'ReqNationGold',
name: 'reqNationGold',
requires: (ctx) => {
const reqs: RequirementKey[] = [...requirements];
if (ctx.nationId !== undefined) {
@@ -119,7 +119,7 @@ export const reqNationRice = (
getRequiredRice: (ctx: ConstraintContext, view: StateView) => number,
requirements: RequirementKey[] = []
): Constraint => ({
name: 'ReqNationRice',
name: 'reqNationRice',
requires: (ctx) => {
const reqs: RequirementKey[] = [...requirements];
if (ctx.nationId !== undefined) {
@@ -150,15 +150,15 @@ export const reqNationRice = (
});
export const existsDestNation = (): Constraint => ({
name: 'ExistsDestNation',
name: 'existsDestNation',
requires: (ctx) =>
resolveDestNationId(ctx) !== undefined
? [
{
kind: 'destNation',
id: resolveDestNationId(ctx) ?? 0,
},
]
{
kind: 'destNation',
id: resolveDestNationId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destNationId = resolveDestNationId(ctx);
@@ -178,7 +178,7 @@ export const existsDestNation = (): Constraint => ({
});
export const differentDestNation = (): Constraint => ({
name: 'DifferentDestNation',
name: 'differentDestNation',
requires: (ctx) => {
const reqs: RequirementKey[] = [];
if (ctx.nationId !== undefined) {
@@ -217,7 +217,7 @@ export const differentDestNation = (): Constraint => ({
});
export const reqNationGeneralCount = (min: number): Constraint => ({
name: 'ReqNationGeneralCount',
name: 'reqNationGeneralCount',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'generalList' }];
if (ctx.nationId !== undefined) {
@@ -259,7 +259,7 @@ export const reqNationGeneralCount = (min: number): Constraint => ({
});
export const checkNationNameDuplicate = (name: string): Constraint => ({
name: 'CheckNationNameDuplicate',
name: 'checkNationNameDuplicate',
requires: () => [{ kind: 'nationList' }],
test: (ctx, view) => {
const nations = view.get({ kind: 'nationList' }) as Nation[] | null;
+2 -2
View File
@@ -3,7 +3,7 @@ import { allow, unknownOrDeny } from './helpers.js';
import type { Constraint, RequirementKey } from './types.js';
export const mustBeTroopLeader = (): Constraint => ({
name: 'MustBeTroopLeader',
name: 'mustBeTroopLeader',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId };
@@ -22,7 +22,7 @@ export const mustBeTroopLeader = (): Constraint => ({
});
export const reqTroopMembers = (): Constraint => ({
name: 'ReqTroopMembers',
name: 'reqTroopMembers',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, { kind: 'generalList' }],
test: (ctx, view) => {
const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId };
+1 -1
View File
@@ -1,6 +1,6 @@
export type ConstraintResult =
| { kind: 'allow' }
| { kind: 'deny'; reason: string; code?: string }
| { kind: 'deny'; reason: string; code?: string; constraintName?: string }
| { kind: 'unknown'; missing: RequirementKey[] };
export type RequirementKey =