import type { General, GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js'; import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js'; import type { RandomGenerator } from '@sammo-ts/common'; const generalActionEventBrand: unique symbol = Symbol('GeneralActionEvent'); export interface GeneralActionEventPayloadMap { 'item.purchased': { itemKey: string; slot: 'horse' | 'weapon' | 'book' | 'item'; }; 'item.sold': { itemKey: string; slot: 'horse' | 'weapon' | 'book' | 'item'; }; 'strategy.succeeded': { consumedItems: readonly string[]; }; 'city.conquered': { attacker: General; }; } export type GeneralActionEventType = keyof GeneralActionEventPayloadMap; export type GeneralActionEventContext< K extends GeneralActionEventType, TriggerState extends GeneralTriggerState = GeneralTriggerState, > = K extends 'item.sold' | 'city.conquered' ? GeneralActionContext & { rng: RandomGenerator } & (K extends 'item.sold' ? { time: NonNullable['time']> } : object) : GeneralActionContext; /** * 레거시 문자열/phase/aux 삼중항을 닫힌 이벤트 계약으로 투영합니다. * * unique-symbol 필드는 런타임 분기용이 아니라 서로 다른 이벤트 payload가 * 구조적으로 우연히 호환되는 것을 막는 nominal shadow type입니다. 이벤트는 * 반드시 createGeneralActionEvent()로 만들며, handler는 같은 K만 반환합니다. */ export type GeneralActionEvent< K extends GeneralActionEventType, _TriggerState extends GeneralTriggerState = GeneralTriggerState, > = Readonly<{ type: K; payload: Readonly; [generalActionEventBrand]: K; }>; export type GeneralActionEventHandler = { bivarianceHack( context: GeneralActionEventContext, event: GeneralActionEvent ): GeneralActionEvent | void; }['bivarianceHack']; export type GeneralActionEventHandlers = Partial<{ [K in GeneralActionEventType]: GeneralActionEventHandler; }>; export const createGeneralActionEvent = < TriggerState extends GeneralTriggerState = GeneralTriggerState, K extends GeneralActionEventType = GeneralActionEventType, >( type: K, payload: GeneralActionEventPayloadMap[K] ): GeneralActionEvent => ({ type, payload, [generalActionEventBrand]: type, }); export const dispatchGeneralActionEventHandlers = < TriggerState extends GeneralTriggerState, K extends GeneralActionEventType, >( handlers: GeneralActionEventHandlers | null | undefined, context: GeneralActionEventContext, event: GeneralActionEvent ): GeneralActionEvent => { // Mapped-type lookup preserves K, but TypeScript cannot currently retain // that correlation through a generic indexed access. This single local // assertion is the proof boundary; module authors and call sites remain // fully checked without unknown/any casts. const handler = handlers?.[event.type] as GeneralActionEventHandler | undefined; return handler?.(context, event) ?? event; };