refactor(logic): replace arbitrary action hooks with typed events
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# 장수 행동 모듈 프로토콜
|
||||
|
||||
`packages/logic/src/actionModules/`는 ref의 `iAction`을 그대로 복사한 범용
|
||||
interface가 아니라, 실제 core2026 실행 경계만 타입으로 표현합니다. 계산
|
||||
hook, 우선순위 trigger, 의미 이벤트는 서로 다른 실행 계약입니다.
|
||||
|
||||
## 세 가지 실행 계약
|
||||
|
||||
| 계약 | core2026 경계 | 실행 의미 |
|
||||
| ---------------- | ------------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| 순차 계산 hook | `GeneralActionPipeline` | ref의 `General::getActionList()` 순서대로 값을 fold합니다. |
|
||||
| 우선순위 trigger | `triggers/core.ts`, `triggers/general.ts`, 전투 trigger | priority, 삽입 순서와 unique ID 중복 제거를 보존한 뒤 fire합니다. |
|
||||
| 의미 이벤트 | `actionModules/events.ts` | 닫힌 이벤트별 payload와 context를 동기적으로 순회합니다. |
|
||||
|
||||
priority trigger를 의미 이벤트로 바꾸거나, 의미 이벤트를 `TriggerCaller`로
|
||||
감싸지 않습니다. 두 경로는 정렬과 중복 제거 의미가 다릅니다.
|
||||
|
||||
## ref 순서와 소유권
|
||||
|
||||
정기턴은 `loadActionModuleBundle()`에서, 전투 시뮬레이터는 같은
|
||||
`createRefOrderedActionStack()` factory에서 제품용 action stack을
|
||||
조립합니다. 순서는 다음과 같습니다.
|
||||
|
||||
1. 국가 타입
|
||||
2. 관직
|
||||
3. 내정 특기
|
||||
4. 전투 특기
|
||||
5. 성격
|
||||
6. 병종
|
||||
7. 계승 버프
|
||||
8. 시나리오 효과
|
||||
9. 아이템
|
||||
|
||||
`RefOrderedActionStack`의 readonly unique-symbol brand는 임의 배열을 제품용
|
||||
표준 stack으로 오인하지 않게 하는 shadow type입니다. 모든 slot을 명시하는
|
||||
factory에서만 이 brand를 만들 수 있으며, 예약턴 runtime env에도 spread하지
|
||||
않고 그대로 전달합니다. 현재 시나리오 효과 runtime module은 이식되지 않아
|
||||
해당 slot은 명시적으로 `null`입니다. 따라서 이 brand는 순서와 slot 소유권을
|
||||
증명하며, 시나리오 효과 구현 완료를 뜻하지 않습니다.
|
||||
|
||||
## 닫힌 의미 이벤트
|
||||
|
||||
`GeneralActionEventPayloadMap`이 허용하는 이벤트와 payload의 단일
|
||||
source입니다. 현재 이벤트는 장비 구매·판매, 계략 성공, 도시 점령입니다.
|
||||
|
||||
- 이벤트는 `createGeneralActionEvent()`만 생성합니다. private
|
||||
unique-symbol brand 때문에 객체 literal로 위조할 수 없습니다.
|
||||
- `GeneralActionEventContext<K>`가 이벤트별 필수 능력을 정합니다. 예를 들어
|
||||
판매는 RNG와 연월, 도시 점령은 RNG가 없으면 compile되지 않습니다.
|
||||
- leaf module은 `eventHandlers`에 처리하는 이벤트 key만 선언합니다.
|
||||
- trait, 병종과 item catalog 같은 합성 router만 내부 `handleEvent`를
|
||||
구현합니다. 두 capability는 `never`를 사용한 상호 배타적 union이라 한
|
||||
module에서 동시에 선언할 수 없습니다.
|
||||
- 새 문자열 action name, `phase`, `aux: Record<string, unknown>`를 범용
|
||||
우회로로 추가하지 않습니다.
|
||||
|
||||
새 이벤트를 추가할 때는 payload map과 context 조건을 먼저 추가한 뒤,
|
||||
실제 producer와 필요한 handler만 연결합니다. 존재하지 않는 handler
|
||||
종류를 interface에 선행 추가하지 않습니다.
|
||||
|
||||
## 저장과 RNG 경계
|
||||
|
||||
의미 이벤트는 producer가 가진 객체를 동기적으로 수정합니다. producer는
|
||||
이벤트 전후의 ref mutation 순서를 그대로 유지한 뒤 기존 effect/flush
|
||||
경계에 결과를 전달합니다.
|
||||
|
||||
- 장비 판매는 판매 대금 반영 → 판매 이벤트 → 슬롯 제거 순서입니다.
|
||||
- 도기 판매의 2분기는 ref `choice([gold, rice])`와 같이 index 0이 금,
|
||||
index 1이 쌀입니다.
|
||||
- 도시 점령은 점령 도시의 수비국 장수 전원을 입력 순서로 호출한 뒤 국가
|
||||
멸망 손실을 계산합니다. 이벤트 handler와 멸망 손실은 같은
|
||||
`ConquerCity` RNG 객체를 이어 씁니다.
|
||||
- 계략 성공 아이템 소비는 `consumeOnStrategySuccess`라는 명시 capability로
|
||||
선언하며 다른 임의 action 이름과 공유하지 않습니다.
|
||||
|
||||
## 검증 경계
|
||||
|
||||
`actionModuleEvents.test.ts`는 표준 순서, 이벤트 brand와 잘못된 context의
|
||||
compile 실패를 검증합니다. `itemActionEvents.test.ts`는 도기 분기와
|
||||
연차 경계, 충차·환약 초기 충전, 계략 성공 소비를 검증합니다.
|
||||
`warAftermath.test.ts`는 도시 점령 대상과 공유 RNG 소비 순서를 검증합니다.
|
||||
ref↔core 실제 명령 차등은
|
||||
`turnCommandGeneralMatrix.integration.test.ts`의 도기 판매 fixture가
|
||||
담당합니다.
|
||||
@@ -200,6 +200,22 @@ non-item versions (see `ActionItem/che_저격_매화수전.php`).
|
||||
per-phase activation.
|
||||
- Ensure RNG usage stays deterministic (`RandUtil` everywhere in triggers).
|
||||
|
||||
## Current core2026 boundary
|
||||
|
||||
core2026 does not expose the whole legacy `iAction` surface as one interface.
|
||||
Modifier folds and semantic side effects live under `src/actionModules/`;
|
||||
priority callers remain under `src/triggers/` and the war trigger modules.
|
||||
This separation is required because `TriggerCaller` sorts by priority and
|
||||
deduplicates unique IDs, while semantic events synchronously preserve module
|
||||
order and producer-owned mutation/RNG boundaries.
|
||||
|
||||
The former copied `onArbitraryAction(actionName, phase, aux)` surface was
|
||||
removed. Equipment purchase/sale, strategy success and city conquest now use
|
||||
the closed, nominally branded protocol documented in
|
||||
[장수 행동 모듈 프로토콜](./action-module-protocol.md). This is a core2026
|
||||
typing boundary only; the ref call order and persisted effects remain the
|
||||
compatibility contract.
|
||||
|
||||
## Related Files
|
||||
|
||||
- `legacy/hwe/sammo/iAction.php`
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
이 핸드북은 새 기능을 어디에 넣을지뿐 아니라 요청이 어떤 경계를 지나 상태로 남는지 설명합니다. 먼저
|
||||
[문서 기준선](../reference-baseline.md)을 확인하고, 변경 성격에 따라 다음 순서로 읽어 주세요.
|
||||
|
||||
| 변경하려는 것 | 먼저 읽을 문서 | 주로 확인할 코드 |
|
||||
| -------------------------- | ---------------------------------------------------- | -------------------------------------------- |
|
||||
| 화면·라우팅·조회 API | [시스템 아키텍처](./system-architecture.md) | `app/*-frontend`, `app/*-api` |
|
||||
| 턴 입력·게임 상태 mutation | [요청·턴·저장 흐름](./request-turn-persistence.md) | `app/game-api`, `app/game-engine` |
|
||||
| 명령·전투·월간 로직 | [도메인 로직과 핵심 클래스](./domain-and-classes.md) | `packages/logic`, `app/game-engine/src/turn` |
|
||||
| 새 파일 위치·검증 범위 | [파일 지도와 변경 절차](./code-map.md) | package manifest, test, docs |
|
||||
| 변경하려는 것 | 먼저 읽을 문서 | 주로 확인할 코드 |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| 화면·라우팅·조회 API | [시스템 아키텍처](./system-architecture.md) | `app/*-frontend`, `app/*-api` |
|
||||
| 턴 입력·게임 상태 mutation | [요청·턴·저장 흐름](./request-turn-persistence.md) | `app/game-api`, `app/game-engine` |
|
||||
| 명령·전투·월간 로직 | [도메인 로직과 핵심 클래스](./domain-and-classes.md), [장수 행동 모듈 프로토콜](../architecture/action-module-protocol.md) | `packages/logic`, `app/game-engine/src/turn` |
|
||||
| 새 파일 위치·검증 범위 | [파일 지도와 변경 절차](./code-map.md) | package manifest, test, docs |
|
||||
|
||||
## 읽을 때 지켜야 할 경계
|
||||
|
||||
|
||||
Reference in New Issue
Block a user