독자 수준별 구조 문서와 게이머 입문·전술 안내를 재정비한다
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
# 장수 행동 모듈 프로토콜
|
||||
|
||||
`packages/logic/src/actionModules/`는 core2026 실행 경계를 타입으로
|
||||
표현합니다. 계산
|
||||
hook, 우선순위 trigger, 의미 이벤트는 서로 다른 실행 계약입니다.
|
||||
명령 하나의 효과는 장수의 특기·병종·장비 등 여러 요소를 거쳐 계산됩니다.
|
||||
**행동 모듈**은 이 추가 규칙들을 조립하는 단위입니다. 먼저
|
||||
[도메인과 조립](../developer/domain-and-classes.md)을 읽으면 아래 타입 이름을 따라가기 쉽습니다.
|
||||
|
||||
`packages/logic/src/actionModules/`는 세 종류의 실행 경계를 구분합니다.
|
||||
**fold**는 앞의 계산 결과를 다음 계산에 넘기는 순차 처리, **trigger**는 정한 조건과
|
||||
우선순위로 발동하는 효과, **의미 이벤트**는 도시 점령처럼 이름 붙인 게임 사건입니다.
|
||||
예를 들어 값을 두 배로 한 뒤 10을 더하는 것과 10을 더한 뒤 두 배로 하는 것은
|
||||
다릅니다. 아래 조립 순서를 보존하는 이유입니다.
|
||||
|
||||
## 세 가지 실행 계약
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# API input-event 재실행·복구 계약
|
||||
|
||||
요청을 처리했지만 응답을 받지 못한 사용자가 다시 시도할 때, 상태를 두 번 바꾸지
|
||||
않으면서 원래 결과를 돌려주기 위한 계약입니다. 여기서 **replay**는 저장한 API
|
||||
응답의 재사용이며 NPC 판단이나 게임 전체의 재실행과는 다릅니다.
|
||||
전체 흐름은 [요청·턴·저장](../developer/request-turn-persistence.md)에 있습니다.
|
||||
|
||||
## 목적과 범위
|
||||
|
||||
game-api mutation은 HTTP `Idempotency-Key`를 profile·인증 actor와 함께 scope한 base ID,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 전투 시뮬레이터 브라우저 실행 경계
|
||||
|
||||
**Web Worker**는 브라우저 화면과 별도로 계산을 실행하는 기능입니다. 긴 전투 반복
|
||||
계산으로 화면이 멎지 않도록 사용합니다. 이 문서는 시뮬레이션의 경계이며 실제
|
||||
게임 턴의 전투를 브라우저가 확정한다는 뜻이 아닙니다. 실제 상태 저장은
|
||||
[요청·턴·저장](../developer/request-turn-persistence.md)의 엔진 경계를 따릅니다.
|
||||
|
||||
## 요청과 권위 데이터
|
||||
|
||||
`BattleSimulatorView.vue`는 화면을 열 때 `battle.getSimulatorContext`에서 form
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# game-api 턴 데몬 procedure/transaction inventory
|
||||
|
||||
이 문서는 당시 조사한 API와 저장 책임의 검토 목록입니다. 아래 route 개수와 줄 번호는
|
||||
조사 기준선의 탐색 자료이며, 현재 전체 개수·위치는 router와 관련 검사로 다시
|
||||
확인합니다. 처음 읽을 때는 [요청·턴·저장](../developer/request-turn-persistence.md)을
|
||||
먼저 읽고 API transaction과 ENGINE transaction의 차이를 익히세요.
|
||||
|
||||
## 범위와 판정 기준
|
||||
|
||||
`app/game-api/src/router/**`의 mutation에서 직접 또는 router 전용 helper를 거쳐
|
||||
@@ -36,25 +41,25 @@ selection-pool create/reselect는 client request ID가 있을 때
|
||||
|
||||
## ENGINE procedure로 전환된 route
|
||||
|
||||
| route | 현재 procedure / API-side 작업 | ENGINE 소유 근거 |
|
||||
| --- | --- | --- |
|
||||
| `general.vacation`, `general.setMySetting`, `general.dropItem` | `engineAuthedProcedure`/`accessEngineAuthedInputProcedure`; session-owned general 조회만 수행 (`app/game-api/src/router/general/index.ts:767-815`) | ENGINE이 general 존재, 현재 설정과 item 보유를 다시 검증하고 변경 (`app/game-engine/src/turn/worldCommandHandler.ts:1712-1841`) |
|
||||
| `nation.appoint`, `nation.changePermission`, `nation.kick` | `engineAuthedProcedure`; session actor 조회만 수행 (`app/game-api/src/router/nation/endpoints/appoint.ts:7-32`, `changePermission.ts:7-35`, `kick.ts:7-24`) | ENGINE이 actor 직위, 국가, 대상/도시를 다시 검증 (`app/game-engine/src/turn/worldCommandHandler.ts:1894-2291`) |
|
||||
| `troop.create`, `troop.join`, `troop.exit`, `troop.kick`, `troop.rename` | `engineAuthedProcedure`; actor 및 조기 권한/대상 조회, API DB write 없음 (`app/game-api/src/router/troop/index.ts:429-577`) | membership/leader/nation/name 검증과 mutation을 ENGINE이 소유 (`app/game-engine/src/turn/worldCommandHandler.ts:1131-1430`) |
|
||||
| `auction.openBuyRice`, `auction.openSellRice`, `auction.openUnique`, `auction.bidBuyRice`, `auction.bidSellRice`, `auction.bidUnique` | `engineAuthedProcedure`; auction/general/world 조기 validation과 commit 뒤 Redis timer index 갱신 (`app/game-api/src/router/auction/index.ts:337-662`) | open/bid DB mutation과 경합/resource 재검증은 ENGINE transaction (`app/game-engine/src/turn/worldCommandHandler.ts:1859-1885`, `app/game-engine/src/auction/bidder.ts`). Redis zset은 durable auction row에서 재구성하는 scheduler index다. |
|
||||
| `inherit.openUniqueAuction` | `engineAuthedProcedure`; world/general/minimum bid 조기 validation (`app/game-api/src/router/inherit/index.ts:389-428`) | 공통 `auctionOpen` ENGINE handler가 mutation을 소유하고 API는 Redis timer index만 갱신 (`app/game-api/src/auction/open.ts:22-51`) |
|
||||
| `join.getSelectionPool` | `engineAuthedProcedure`; actor와 accepted game time만 전달 (`app/game-api/src/router/join/index.ts:390-406`) | `selectPoolReserve` ENGINE handler가 world/DB 상태에서 예약을 재검증하고 저장 (`app/game-engine/src/turn/worldCommandHandler.ts:401-437`) |
|
||||
| `inherit.buyHiddenBuff`, `inherit.setNextSpecialWar`, `inherit.resetSpecialWar`, `inherit.resetTurnTime`, `inherit.resetStat`, `inherit.buyRandomUnique`, `inherit.checkOwner` | 모두 `engineAuthedProcedure`; 인증 user ID와 action 입력만 전달 (`app/game-api/src/router/inherit/index.ts:107-124`, `:301-388`, `:429-445`) | `inheritanceAction` ENGINE handler가 general/user 소유권, 통일 상태, 잔액, 대상, RNG, general patch, inheritance point/log/message를 한 transaction에서 처리 (`app/game-engine/src/turn/worldCommandHandler.ts:807-818`, `app/game-engine/src/turn/inheritanceActionService.ts:313-669`) |
|
||||
| `nation.setNotice`, `nation.setScoutMsg`, `nation.setSecretLimit`, `nation.setRate`, `nation.setBlockWar`, `nation.setBill`, `nation.setBlockScout` | `engineAuthedProcedure`; API는 인증 actor, 현재 국가와 조기 권한만 읽고 semantic mutation을 전달 (`app/game-api/src/router/nation/endpoints/setNotice.ts`, `setScoutMsg.ts`, `setSecretLimit.ts`, `setRate.ts`, `setBlockWar.ts`, `setBill.ts`, `setBlockScout.ts`) | `setNationSetting`이 실행 시점 owner/nation/직책·permission을 다시 검사한다. 전쟁 설정 잔여 횟수 차감과 임관 잠금 검사를 현재 ENGINE state에서 수행하고, 공지는 logical game time과 author snapshot을 국가 meta와 같은 transaction에 저장한다 (`app/game-engine/src/turn/nationSettingMutation.ts`, `worldCommandHandler.ts`). |
|
||||
| `npc.setNationPolicy`, `npc.setNationPriority`, `npc.setGeneralPriority` | `accessEngineAuthedInputProcedure`; API는 현재 화면값과 unit-set 기반 입력 보조만 수행하고 actor-bound semantic delta와 명시적 nullable revision을 전달 (`app/game-api/src/router/npc/index.ts`) | `setNpcPolicy`가 실행 시점 owner/nation/permission, strict CAS, 현재 troop/city membership, priority와 numeric policy를 검증하고 logical setter snapshot과 delta만 반영한다 (`app/game-engine/src/turn/npcPolicyMutation.ts`, `worldCommandHandler.ts`). |
|
||||
| route | 현재 procedure / API-side 작업 | ENGINE 소유 근거 |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `general.vacation`, `general.setMySetting`, `general.dropItem` | `engineAuthedProcedure`/`accessEngineAuthedInputProcedure`; session-owned general 조회만 수행 (`app/game-api/src/router/general/index.ts:767-815`) | ENGINE이 general 존재, 현재 설정과 item 보유를 다시 검증하고 변경 (`app/game-engine/src/turn/worldCommandHandler.ts:1712-1841`) |
|
||||
| `nation.appoint`, `nation.changePermission`, `nation.kick` | `engineAuthedProcedure`; session actor 조회만 수행 (`app/game-api/src/router/nation/endpoints/appoint.ts:7-32`, `changePermission.ts:7-35`, `kick.ts:7-24`) | ENGINE이 actor 직위, 국가, 대상/도시를 다시 검증 (`app/game-engine/src/turn/worldCommandHandler.ts:1894-2291`) |
|
||||
| `troop.create`, `troop.join`, `troop.exit`, `troop.kick`, `troop.rename` | `engineAuthedProcedure`; actor 및 조기 권한/대상 조회, API DB write 없음 (`app/game-api/src/router/troop/index.ts:429-577`) | membership/leader/nation/name 검증과 mutation을 ENGINE이 소유 (`app/game-engine/src/turn/worldCommandHandler.ts:1131-1430`) |
|
||||
| `auction.openBuyRice`, `auction.openSellRice`, `auction.openUnique`, `auction.bidBuyRice`, `auction.bidSellRice`, `auction.bidUnique` | `engineAuthedProcedure`; auction/general/world 조기 validation과 commit 뒤 Redis timer index 갱신 (`app/game-api/src/router/auction/index.ts:337-662`) | open/bid DB mutation과 경합/resource 재검증은 ENGINE transaction (`app/game-engine/src/turn/worldCommandHandler.ts:1859-1885`, `app/game-engine/src/auction/bidder.ts`). Redis zset은 durable auction row에서 재구성하는 scheduler index다. |
|
||||
| `inherit.openUniqueAuction` | `engineAuthedProcedure`; world/general/minimum bid 조기 validation (`app/game-api/src/router/inherit/index.ts:389-428`) | 공통 `auctionOpen` ENGINE handler가 mutation을 소유하고 API는 Redis timer index만 갱신 (`app/game-api/src/auction/open.ts:22-51`) |
|
||||
| `join.getSelectionPool` | `engineAuthedProcedure`; actor와 accepted game time만 전달 (`app/game-api/src/router/join/index.ts:390-406`) | `selectPoolReserve` ENGINE handler가 world/DB 상태에서 예약을 재검증하고 저장 (`app/game-engine/src/turn/worldCommandHandler.ts:401-437`) |
|
||||
| `inherit.buyHiddenBuff`, `inherit.setNextSpecialWar`, `inherit.resetSpecialWar`, `inherit.resetTurnTime`, `inherit.resetStat`, `inherit.buyRandomUnique`, `inherit.checkOwner` | 모두 `engineAuthedProcedure`; 인증 user ID와 action 입력만 전달 (`app/game-api/src/router/inherit/index.ts:107-124`, `:301-388`, `:429-445`) | `inheritanceAction` ENGINE handler가 general/user 소유권, 통일 상태, 잔액, 대상, RNG, general patch, inheritance point/log/message를 한 transaction에서 처리 (`app/game-engine/src/turn/worldCommandHandler.ts:807-818`, `app/game-engine/src/turn/inheritanceActionService.ts:313-669`) |
|
||||
| `nation.setNotice`, `nation.setScoutMsg`, `nation.setSecretLimit`, `nation.setRate`, `nation.setBlockWar`, `nation.setBill`, `nation.setBlockScout` | `engineAuthedProcedure`; API는 인증 actor, 현재 국가와 조기 권한만 읽고 semantic mutation을 전달 (`app/game-api/src/router/nation/endpoints/setNotice.ts`, `setScoutMsg.ts`, `setSecretLimit.ts`, `setRate.ts`, `setBlockWar.ts`, `setBill.ts`, `setBlockScout.ts`) | `setNationSetting`이 실행 시점 owner/nation/직책·permission을 다시 검사한다. 전쟁 설정 잔여 횟수 차감과 임관 잠금 검사를 현재 ENGINE state에서 수행하고, 공지는 logical game time과 author snapshot을 국가 meta와 같은 transaction에 저장한다 (`app/game-engine/src/turn/nationSettingMutation.ts`, `worldCommandHandler.ts`). |
|
||||
| `npc.setNationPolicy`, `npc.setNationPriority`, `npc.setGeneralPriority` | `accessEngineAuthedInputProcedure`; API는 현재 화면값과 unit-set 기반 입력 보조만 수행하고 actor-bound semantic delta와 명시적 nullable revision을 전달 (`app/game-api/src/router/npc/index.ts`) | `setNpcPolicy`가 실행 시점 owner/nation/permission, strict CAS, 현재 troop/city membership, priority와 numeric policy를 검증하고 logical setter snapshot과 delta만 반영한다 (`app/game-engine/src/turn/npcPolicyMutation.ts`, `worldCommandHandler.ts`). |
|
||||
|
||||
이 표의 **36개 route**와 아래 설문 전환 **1개 route**를 합쳐 **37개 route**다.
|
||||
|
||||
## 혼합 또는 validation 이관이 먼저 필요한 route
|
||||
|
||||
| route | 현재 procedure / outer transaction | 보류 근거 |
|
||||
| --- | --- | --- |
|
||||
| `messages.respond` | `authedProcedure`, action별 분기 (`app/game-api/src/router/messages/index.ts:318-372`) | `scout`/`raiseInvader`는 `messageRespond` ENGINE command가 처리하지만 `noAggression`/`cancelNA`/`stopWar`는 API transaction의 `respondToDiplomaticMessage` 경로가 처리한다. route 전체를 ENGINE-owned로 보지 않는다. |
|
||||
| route | 현재 procedure / outer transaction | 보류 근거 |
|
||||
| ---------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `messages.respond` | `authedProcedure`, action별 분기 (`app/game-api/src/router/messages/index.ts:318-372`) | `scout`/`raiseInvader`는 `messageRespond` ENGINE command가 처리하지만 `noAggression`/`cancelNA`/`stopWar`는 API transaction의 `respondToDiplomaticMessage` 경로가 처리한다. route 전체를 ENGINE-owned로 보지 않는다. |
|
||||
| `tournament.join`, `tournament.placeBet` | `engineAuthedProcedure`, API outer 없음 (`app/game-api/src/router/tournament/index.ts`) | PostgreSQL ENGINE resource/meta 명령과 Redis-owned participants/bets를 결합하고 실패 시 보상 ENGINE 명령을 보낸다. API clock advisory lock을 잡은 채 child ENGINE transaction을 기다리지 않으며, command에는 HTTP request-scoped step ID를 전달한다. 여전히 하나의 DB transaction이 아니므로 durable saga/reconciliation이 필요하다. |
|
||||
|
||||
합계 **3개 route**다.
|
||||
@@ -77,11 +82,11 @@ mutation lock → API input-event/DB clock fence → 상태 변경 → commit
|
||||
|
||||
## 기존에 API outer transaction이 없던 ENGINE route
|
||||
|
||||
| route | 근거 |
|
||||
| --- | --- |
|
||||
| `general.adjustIcon` | `engineAuthedProcedure`; helper가 stable account-icon request ID로 ENGINE command를 보냄 (`app/game-api/src/router/general/index.ts:694-720`, `app/game-api/src/services/accountIconSync.ts:40-75`) |
|
||||
| route | 근거 |
|
||||
| ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `general.adjustIcon` | `engineAuthedProcedure`; helper가 stable account-icon request ID로 ENGINE command를 보냄 (`app/game-api/src/router/general/index.ts:694-720`, `app/game-api/src/services/accountIconSync.ts:40-75`) |
|
||||
| `general.ensureDieOnPrestartStatus`, `general.dieOnPrestart`, `general.buildNationCandidate`, `general.instantRetreat` | `accessEngineAuthedProcedure`/`accessEngineAuthedInputProcedure`; user/general 조회는 outer transaction 밖이고 command마다 stable request ID가 있음 (`app/game-api/src/router/general/index.ts:104-144`, `:722-766`) |
|
||||
| `join.selectPoolGeneral`, `join.reselectPoolGeneral`, `join.createGeneral`, `join.possessGeneral` | `engineAuthedProcedure`; client request ID가 있으면 user-scoped durable identity를 사용 (`app/game-api/src/router/join/index.ts:407-564`, `:608-642`) |
|
||||
| `join.selectPoolGeneral`, `join.reselectPoolGeneral`, `join.createGeneral`, `join.possessGeneral` | `engineAuthedProcedure`; client request ID가 있으면 user-scoped durable identity를 사용 (`app/game-api/src/router/join/index.ts:407-564`, `:608-642`) |
|
||||
|
||||
합계 **9개 route**다.
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Game clock reconciliation
|
||||
|
||||
게임을 멈춘 뒤 다시 시작할 때 여러 기능의 남은 시간과 순서를 함께 맞추는 계약입니다.
|
||||
**재정렬(reconciliation)**은 화면 날짜만 고치는 일이 아니라 DB 일정·worker 기한·
|
||||
메모리 상태를 같은 세대로 전환하는 과정입니다. 개념은 [게임 시계](./game-clock.md),
|
||||
실패 대응은 [복구 안내](../developer/game-clock-recovery.md)를 먼저 읽으세요.
|
||||
아래 `EXACT`는 역사적 정책이고 현재 maintenance·장애 복구는 `RECOVER_TURNS`입니다.
|
||||
|
||||
## Product contract
|
||||
|
||||
Gameplay time is an integer `GameTick`; one turn is permanently `36,000,000`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 게임 시계
|
||||
|
||||
예약된 행동까지 얼마나 남았는지와 로그인·소유권이 현실에서 언제 만료되는지는
|
||||
다른 질문입니다. 그래서 게임을 멈출 때 모든 시간을 한꺼번에 멈추지 않습니다.
|
||||
이 문서는 그 구분을 설명하고, 상세 필드 목록은 아래 시간 도메인 문서로 연결합니다.
|
||||
|
||||
시간 규칙은 `GAME_TIME`, `WALL_TIME`, `MONOTONIC_ELAPSED_TIME`로
|
||||
나뉩니다. 게임 진행의 권위는 `world_state.clock_tick`, 영속 wall
|
||||
판정의 권위는 PostgreSQL UTC 시계, 프로세스 내부 경과시간의 권위는
|
||||
@@ -67,14 +71,20 @@ column을 9시간 미래로 쓰지 않습니다.
|
||||
|
||||
## 중단 후 재개
|
||||
|
||||
realtime daemon은 재개할 때 Ref `checkDelay()`와 같은 한도를 적용합니다.
|
||||
밀린 완전 턴 수가 턴 간격 20분 이상이면 1턴, 10분 이상이면 3턴, 그보다
|
||||
짧으면 6턴을 초과할 때 장기 중단으로 봅니다. 한도 이내의 짧은 중단은 턴을
|
||||
순서대로 실행해 따라잡고, 한도를 넘으면 밀린 완전 턴만큼 `last_turn_tick`,
|
||||
전 장수의 `turn_tick`, 미완료 경매의 `close_tick`과 각각의 DateTime 투영값을
|
||||
한 transaction에서 옮깁니다. 이 보정은 명령이나 월 이벤트를 실행하지 않으므로
|
||||
건너뛴 기간의 RNG를 소비하지 않습니다. 이미 처리 중 anchor 갱신으로 표시
|
||||
시각이 늦어진 상태도 현재 wall time에 맞추되 game tick은 되감지 않습니다.
|
||||
현재 maintenance·장애 복구는 `RECOVER_TURNS` 정책을 사용합니다. 전체 중단이
|
||||
현실 시간 10분 이내면 기존 실행 순서와 budget으로 밀린 턴을 처리합니다.
|
||||
그보다 길면 밀린 시간 중 완전한 12턴 묶음을 일정 이동으로 건너뛰고, 나머지
|
||||
지연은 월 경계에 맞춘 복구 구간에서 두 배 속도로 실행해 정상 시간표에 합류합니다.
|
||||
설정된 기본 턴 간격 자체를 절반으로 변경하지 않습니다.
|
||||
|
||||
건너뛴 구간에는 게임 행동·월 이벤트를 실행하지 않으며, 따라잡는 구간에는 실제
|
||||
행동을 순서대로 실행합니다. DB에 복구 구간을 보존하므로 프로세스 교체가 새 복구를
|
||||
중복 적용해서는 안 됩니다. 가오픈·통일 대기와 명시적 정지는 별도 상태 검사를 거칩니다.
|
||||
|
||||
기준 구현은 `packages/common/src/time/TurnRecovery.ts`,
|
||||
`app/game-engine/src/turn/prepareRealtimeRecovery.ts`, `clockReconciliation.ts`입니다.
|
||||
계산식·참여자·Redis 투영까지의 절차는 [시계 재정렬](./game-clock-reconciliation.md),
|
||||
실패 후 관찰과 재시도는 [복구 안내](../developer/game-clock-recovery.md)에 있습니다.
|
||||
|
||||
운영자가 명시적으로 일정을 지연하거나 가속하려면 Gateway 작업을 사용합니다.
|
||||
이 작업은 `clock_base_time`과 DateTime 투영값을 같이 이동하고, game tick 및
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# core2026 아키텍처
|
||||
|
||||
이 문서는 프로그램들의 책임을 한눈에 보는 지도입니다. 웹 서버가 처음이라면
|
||||
[기초 구조 안내](../developer/first-steps.md), 개발 경험이 있다면
|
||||
[시스템 읽기](../developer/system-walkthrough.md)를 먼저 읽어도 좋습니다.
|
||||
세부 운영 절차는 [런타임](./runtime.md)에 있습니다.
|
||||
|
||||
## 시스템 경계
|
||||
|
||||
`core2026`은 계정·profile 운영을 담당하는 gateway와 profile별 게임 런타임을
|
||||
@@ -35,6 +40,10 @@ PostgreSQL 상태와 결합해 서버에서 결정합니다.
|
||||
- `app/gateway-api/src/orchestrator/`는 DB operation queue, commit별 worktree,
|
||||
build, PM2 process와 예약 상태를 조정합니다.
|
||||
|
||||
Gateway 자체를 업데이트하는 `app/release-controller`는 Gateway 밖에서 실행합니다.
|
||||
게임 profile 작업을 담당하는 orchestrator와 Gateway 전체 릴리스 담당을 나누어,
|
||||
업데이트 대상 프로세스가 자신의 종료 이후 단계까지 직접 책임지지 않도록 합니다.
|
||||
|
||||
### Game
|
||||
|
||||
- `app/game-frontend`는 profile base path에서 실행하는 Vue SPA입니다.
|
||||
@@ -70,6 +79,14 @@ Command는 `GeneralActionDefinition` 또는 국가 command module로 args,
|
||||
constraint, turn metadata, 결과와 로그를 선언합니다. 런타임 context와
|
||||
persistence는 `app/game-engine`이 제공합니다.
|
||||
|
||||
### 자주 나오는 용어
|
||||
|
||||
- **registry**: 이름으로 사용할 명령·효과를 찾아 주는 등록 목록입니다.
|
||||
- **loader**: 파일이나 DB 값을 읽어 실행에 필요한 형태로 만드는 코드입니다.
|
||||
- **context**: 한 행동을 계산할 때 필요한 장수·도시·시각·난수 등의 입력 묶음입니다.
|
||||
- **hook·trigger**: 정해진 계산 또는 사건 지점에 연결하는 추가 효과입니다.
|
||||
- **persistence**: 재시작해도 남도록 저장하는 일입니다.
|
||||
|
||||
### `packages/infra`
|
||||
|
||||
`prisma/gateway.prisma`와 `prisma/game.prisma`가 영속 schema의 기준입니다.
|
||||
@@ -99,7 +116,9 @@ DB commit의 대체 조건으로 사용하지 않습니다.
|
||||
|
||||
## 시나리오와 profile
|
||||
|
||||
`profile`은 규칙·자산 계열이고 `scenario`는 게임 초기 데이터입니다.
|
||||
운영 화면의 프로필은 독립적으로 실행·배포·진행 상태를 관리하는 게임 대상입니다.
|
||||
설정에서 `profile`은 규칙·자산 계열을, `scenario`는 선택한 시나리오를 나타냅니다.
|
||||
서로 다른 곳에서 쓰는 같은 이름을 하나의 값으로 혼동하지 마세요.
|
||||
런타임 식별자는 `${profile}:${scenario}`입니다. Scenario JSON은
|
||||
`resources/scenario`에서 합성되고 zod parser를 거쳐 seeder와 runtime에
|
||||
전달됩니다. map, unit set, turn-command profile도 resource loader를 통해
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
|
||||
## 의존 방향
|
||||
|
||||
제품 소스의 의존 방향은 다음과 같습니다.
|
||||
패키지는 함께 재사용할 코드의 묶음입니다. **의존 방향**은 어느 묶음이 다른
|
||||
묶음의 코드를 가져오는지 뜻하며, 서버 사이의 네트워크 통신 방향과는 다릅니다.
|
||||
아래는 `tools/check-package-boundaries.mjs`가 허용하는 workspace 의존입니다.
|
||||
허용 목록은 모든 파일이 실제로 전부 import한다는 뜻은 아닙니다.
|
||||
|
||||
```text
|
||||
packages/common
|
||||
↑
|
||||
packages/logic ← packages/infra
|
||||
↑ ↑
|
||||
└──── app/game-engine ────┐
|
||||
↑ │
|
||||
app/game-api app/gateway-api
|
||||
↑ ↑
|
||||
game-frontend gateway-frontend
|
||||
```
|
||||
| 가져오는 쪽 | 가져올 수 있는 workspace 코드 |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| common | 다른 workspace package 없음 |
|
||||
| logic | common |
|
||||
| infra | common, logic |
|
||||
| game-engine | common, logic, infra |
|
||||
| game-api·gateway-api | common, logic, infra, game-engine의 공개 subpath |
|
||||
| release-controller | gateway-api, infra |
|
||||
| game-frontend | common, logic의 브라우저용 값; game-api·gateway-api의 타입 |
|
||||
| gateway-frontend | common의 브라우저용 값; game-api·gateway-api의 타입 |
|
||||
|
||||
화살표의 시작점이 끝점을 import합니다. `packages/logic`은 DB, Redis, 파일,
|
||||
`packages/logic`은 DB, Redis, 파일,
|
||||
네트워크, 환경 변수와 stdout을 직접 사용하지 않습니다. 런타임 관찰이 필요한
|
||||
경우 `packages/logic/src/ports/`에 포트를 선언하고 app 계층에서 구현을
|
||||
주입합니다. Prisma 생성 타입과 connector는 `packages/infra`가 소유하며,
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# 실시간 read-model change journal과 revision-first 조회 설계
|
||||
|
||||
## 읽기 안내
|
||||
|
||||
**read model**은 화면 조회에 맞춰 구성한 데이터, **revision**은 변경 세대를 나타내는
|
||||
번호, **journal**은 어떤 조회 결과가 바뀌었는지 남기는 기록입니다. 서버는 DB 저장을
|
||||
확정한 뒤 필요한 화면에 변경을 알립니다. 브라우저는 알림 자체를 최종 상태로 삼지
|
||||
않고 권한에 맞는 데이터를 다시 조회합니다.
|
||||
|
||||
이 문서는 설계 당시의 병목과 이후 구현·측정을 함께 보존한 상세 자료입니다.
|
||||
아래의 “설계 출발점”은 개선 전 경로이며 현재 전체 호출 흐름으로 읽지 마세요.
|
||||
구현 단계 표와 측정 결과도 해당 날짜·workload의 증거입니다. 운영 서버의 현재 수용량을
|
||||
보장하지 않습니다. 먼저 [요청·턴·저장](../developer/request-turn-persistence.md)을 읽고,
|
||||
변경 생산은 `app/game-engine/src/turn/databaseHooks.ts`, API 전달은
|
||||
`app/game-api/src/realtime/`, 브라우저 반영은
|
||||
`app/game-frontend/src/stores/mainDashboard.ts`에서 확인합니다.
|
||||
|
||||
## 목적
|
||||
|
||||
메인 화면의 실시간 갱신이 실제 화면 변화가 없는 경우에도 viewer별 PostgreSQL
|
||||
@@ -19,9 +34,9 @@ read model을 다시 구성한 뒤 `unchanged`를 판정하는 비용을 제거
|
||||
추산이 아니라 아래 workload를 실제 PostgreSQL·Redis·HTTP/SSE 경계에서 실행한
|
||||
결과로 판정한다.
|
||||
|
||||
## 현재 상태와 병목
|
||||
## 설계 출발점: 개선 전 상태와 병목
|
||||
|
||||
현재 turn daemon은 `InMemoryTurnWorld`의 dirty general/city/nation 후보를 daemon
|
||||
설계 출발점의 turn daemon은 `InMemoryTurnWorld`의 dirty general/city/nation 후보를 daemon
|
||||
수명의 in-memory baseline과 비교한다. `databaseHooks`가 `content`, `map`,
|
||||
`contacts`, `frontStatus`, `lobby` canonical projection을 나누어 비교한 뒤
|
||||
`RealtimeReadModelChanges`를 만든다. 이 단계는 dirty entity만 직렬화하며 일반
|
||||
@@ -46,7 +61,7 @@ readModelInvalidated
|
||||
보통 소속 장수의 context-only 자동 갱신은 access gate를 포함해 약 13 SQL이고,
|
||||
command table까지 포함하면 약 21 SQL이다. 300 viewer가 global 변화 burst를 1초
|
||||
간격으로 받으면 bundle만 이론상 약 3,900~6,300 statement/s까지 커질 수 있다.
|
||||
이는 실제 운영 측정값이 아니라 현재 호출 그래프의 상한식이며, 구현 뒤 실제
|
||||
이는 실제 운영 측정값이 아니라 설계 당시 호출 그래프의 상한식이며, 구현 뒤 실제
|
||||
statement rate로 대체한다.
|
||||
|
||||
## 보존할 계약
|
||||
@@ -162,29 +177,29 @@ statement 수와 row lock 시간을 제한한다. 없는 key의 revision은 0으
|
||||
|
||||
초기 domain은 다음과 같다.
|
||||
|
||||
| domain | entity ID | 의미 |
|
||||
| --- | ---: | --- |
|
||||
| `general.content` | general ID | 현재 장수 context/command/board dependency |
|
||||
| `city.content` | city ID | 현재 도시 context/command dependency |
|
||||
| `nation.content` | nation ID | 현재 국가 context/command/board dependency |
|
||||
| `dashboard.global` | 0 | 부대·예약턴 및 전체 general/city/nation aggregate를 포괄하는 source-only dependency |
|
||||
| `world.content` | 0 | 연월, scenario/config/catalog 성격의 dependency |
|
||||
| `map.world` | 0 | shared base map projection |
|
||||
| `map.general` | general ID | 현재 장수 이동처럼 actor별 map wake-up에 필요한 변화 |
|
||||
| `records.general` | general ID | 개인 최근 기록 |
|
||||
| `records.global` | 0 | 장수 동향 |
|
||||
| `records.history` | 0 | 중원 정세 |
|
||||
| `front.general` | general ID | actor별 front status |
|
||||
| `front.nation` | nation ID | 국가 공지 등 viewer별 front status |
|
||||
| `front.global` | 0 | 설문과 global front status |
|
||||
| `access.general` | general ID | 접속 점수와 제한 상태, public fan-out 없음 |
|
||||
| `lobby.world` | 0 | NPC/국가 수, 공용 lobby projection |
|
||||
| `lobby.general` | general ID | 본인 lobby에 보이는 이름·아이콘 projection |
|
||||
| `contacts.world` | 0 | 장수 목록·외교 연락처 공용 projection |
|
||||
| `reserved.general` | general ID | 장수 예약 명령 projection |
|
||||
| `messages.mailbox` | mailbox ID | commit된 메시지 mailbox wake-up; public SSE에는 ID 미노출 |
|
||||
| `tournament` | 0 | 토너먼트 stage/state |
|
||||
| `betting` | 0 | 국가/토너먼트 베팅 목록·상태 |
|
||||
| domain | entity ID | 의미 |
|
||||
| ------------------ | ---------: | ----------------------------------------------------------------------------------- |
|
||||
| `general.content` | general ID | 현재 장수 context/command/board dependency |
|
||||
| `city.content` | city ID | 현재 도시 context/command dependency |
|
||||
| `nation.content` | nation ID | 현재 국가 context/command/board dependency |
|
||||
| `dashboard.global` | 0 | 부대·예약턴 및 전체 general/city/nation aggregate를 포괄하는 source-only dependency |
|
||||
| `world.content` | 0 | 연월, scenario/config/catalog 성격의 dependency |
|
||||
| `map.world` | 0 | shared base map projection |
|
||||
| `map.general` | general ID | 현재 장수 이동처럼 actor별 map wake-up에 필요한 변화 |
|
||||
| `records.general` | general ID | 개인 최근 기록 |
|
||||
| `records.global` | 0 | 장수 동향 |
|
||||
| `records.history` | 0 | 중원 정세 |
|
||||
| `front.general` | general ID | actor별 front status |
|
||||
| `front.nation` | nation ID | 국가 공지 등 viewer별 front status |
|
||||
| `front.global` | 0 | 설문과 global front status |
|
||||
| `access.general` | general ID | 접속 점수와 제한 상태, public fan-out 없음 |
|
||||
| `lobby.world` | 0 | NPC/국가 수, 공용 lobby projection |
|
||||
| `lobby.general` | general ID | 본인 lobby에 보이는 이름·아이콘 projection |
|
||||
| `contacts.world` | 0 | 장수 목록·외교 연락처 공용 projection |
|
||||
| `reserved.general` | general ID | 장수 예약 명령 projection |
|
||||
| `messages.mailbox` | mailbox ID | commit된 메시지 mailbox wake-up; public SSE에는 ID 미노출 |
|
||||
| `tournament` | 0 | 토너먼트 stage/state |
|
||||
| `betting` | 0 | 국가/토너먼트 베팅 목록·상태 |
|
||||
|
||||
projection 세부 ID 목록은 Redis wake-up의 viewer filtering에 사용하고, DB revision
|
||||
row는 API가 작은 dependency vector를 읽는 authoritative 경계로 사용한다.
|
||||
@@ -376,12 +391,12 @@ BroadcastChannel 계약을 유지한다. 변경 ID set/boolean은 drop하지 않
|
||||
|
||||
초기 cadence 목표는 다음과 같다.
|
||||
|
||||
| 종류 | 최대 시작 빈도 | 이유 |
|
||||
| --- | ---: | --- |
|
||||
| 자기 context/commands/board | 1초 1회 | 자기 명령 결과의 빠른 반영 |
|
||||
| records/front status | 2초 1회 | global burst 합치기 |
|
||||
| map/lobby/tournament/betting | 5초 1회 | 300 viewer의 shared fan-out 제한 |
|
||||
| access-only non-limited 재확인 | 5초 1회 | 제한 DB gate fan-out 제한 |
|
||||
| 종류 | 최대 시작 빈도 | 이유 |
|
||||
| ------------------------------ | -------------: | -------------------------------- |
|
||||
| 자기 context/commands/board | 1초 1회 | 자기 명령 결과의 빠른 반영 |
|
||||
| records/front status | 2초 1회 | global burst 합치기 |
|
||||
| map/lobby/tournament/betting | 5초 1회 | 300 viewer의 shared fan-out 제한 |
|
||||
| access-only non-limited 재확인 | 5초 1회 | 제한 DB gate fan-out 제한 |
|
||||
|
||||
manual refresh, visible 복귀와 realtime 재활성화는 cadence를 기다리지 않고 fresh
|
||||
snapshot을 한 번 읽는다. 숫자는 실제 혼합 부하 결과에 따라 조정하며, 부하만 낮추기
|
||||
@@ -444,12 +459,12 @@ visible 복귀는 leader가 fresh 3-slice snapshot 한 번을 읽어 pub/sub gap
|
||||
|
||||
### 2026-08-17 구현 상태
|
||||
|
||||
| Phase | 상태 | 현재 근거 |
|
||||
| --- | --- | --- |
|
||||
| A | 완료 | all-false access gate, frontend 강제 context 제거, Chromium realtime trace |
|
||||
| B | 완료 | typed journal, PostgreSQL revision/outbox/meta, engine/API 원자 writer, retry dispatcher, 86 mutation inventory |
|
||||
| C | 완료 | dashboard revision-first, auth/global dependency, durable map cache, 모든 tournament Redis writer 원자화, stage-only main realtime invalidation, coverage v1 activation/rollback integration |
|
||||
| D | 부분 완료 | E1 1,200장수 1개월 deterministic profile과 300 SSE/HTTP 짧은 calibration 완료. E2 actual daemon DB flush 및 30분 M1/R1은 미실행 |
|
||||
| Phase | 상태 | 현재 근거 |
|
||||
| ----- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| A | 완료 | all-false access gate, frontend 강제 context 제거, Chromium realtime trace |
|
||||
| B | 완료 | typed journal, PostgreSQL revision/outbox/meta, engine/API 원자 writer, retry dispatcher, 86 mutation inventory |
|
||||
| C | 완료 | dashboard revision-first, auth/global dependency, durable map cache, 모든 tournament Redis writer 원자화, stage-only main realtime invalidation, coverage v1 activation/rollback integration |
|
||||
| D | 부분 완료 | E1 1,200장수 1개월 deterministic profile과 300 SSE/HTTP 짧은 calibration 완료. E2 actual daemon DB flush 및 30분 M1/R1은 미실행 |
|
||||
|
||||
`부분 완료`는 capacity 합격을 뜻하지 않는다. 이 작업의 수용 추산은 아래 실제 짧은
|
||||
calibration과 E1 결과를 함께 사용하되, 장기 soak/daemon flush 경계는 별도 admission
|
||||
@@ -472,15 +487,15 @@ API와 process 경합을 제외하므로 production 수용 근거로 단독 사
|
||||
|
||||
### workload
|
||||
|
||||
| ID | workload | 필수 관찰값 |
|
||||
| --- | --- | --- |
|
||||
| E1 | 5분 턴, 900 NPC 및 총 1,200장수 DB-free 고정 seed | turns/actions/s, command p95/p99, memory high-water, 최종 state hash |
|
||||
| E2 | 실제 DB flush를 포함한 12 turns/s와 1,200 동시 경계 | 계산/flush/publish, schedule lag, rows/statements, rollback 0 |
|
||||
| A1 | 300 SSE idle 30분 | 연결 성공/유지율, ping, runtime RSS, event-loop lag |
|
||||
| A2 | 300 viewer own context 변화 5분 주기 | HTTP p95/p99, fast unchanged 비율, DB statements/s |
|
||||
| A3 | 초당 3회 map/record 후보 10분 | coalesced request rate, shared cache hit, stale/missed revision 0 |
|
||||
| M1 | E2 + A1 + A2 + A3 혼합 30분 | CPU/RSS/DB/Redis/HTTP/SSE와 daemon lag 전체 |
|
||||
| R1 | Redis publish/cache 장애와 API/daemon restart | durable revision 수렴, full snapshot 1회, 유실/중복 표시 0 |
|
||||
| ID | workload | 필수 관찰값 |
|
||||
| --- | --------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| E1 | 5분 턴, 900 NPC 및 총 1,200장수 DB-free 고정 seed | turns/actions/s, command p95/p99, memory high-water, 최종 state hash |
|
||||
| E2 | 실제 DB flush를 포함한 12 turns/s와 1,200 동시 경계 | 계산/flush/publish, schedule lag, rows/statements, rollback 0 |
|
||||
| A1 | 300 SSE idle 30분 | 연결 성공/유지율, ping, runtime RSS, event-loop lag |
|
||||
| A2 | 300 viewer own context 변화 5분 주기 | HTTP p95/p99, fast unchanged 비율, DB statements/s |
|
||||
| A3 | 초당 3회 map/record 후보 10분 | coalesced request rate, shared cache hit, stale/missed revision 0 |
|
||||
| M1 | E2 + A1 + A2 + A3 혼합 30분 | CPU/RSS/DB/Redis/HTTP/SSE와 daemon lag 전체 |
|
||||
| R1 | Redis publish/cache 장애와 API/daemon restart | durable revision 수렴, full snapshot 1회, 유실/중복 표시 0 |
|
||||
|
||||
### provisional pass gate
|
||||
|
||||
@@ -515,12 +530,12 @@ API와 process 경합을 제외하므로 production 수용 근거로 단독 사
|
||||
1개월/1,200 general turn을 두 번 실행했다. 두 실행의 최종 state SHA-256은 모두
|
||||
`d14a5f451385095bb56f9459928f100bf81864178bac9861686e06001a7f02a0`이었다.
|
||||
|
||||
| 관찰값 | 결과 |
|
||||
| --- | ---: |
|
||||
| 처리량 | 1,283.986 general turns/s |
|
||||
| general turn p95 / p99 | 2.793 / 3.393 ms |
|
||||
| month wall p95 | 934.590 ms |
|
||||
| max RSS | 681,316,352 bytes |
|
||||
| 관찰값 | 결과 |
|
||||
| ---------------------- | ------------------------: |
|
||||
| 처리량 | 1,283.986 general turns/s |
|
||||
| general turn p95 / p99 | 2.793 / 3.393 ms |
|
||||
| month wall p95 | 934.590 ms |
|
||||
| max RSS | 681,316,352 bytes |
|
||||
|
||||
이는 평균 필요량 4 turns/s와 설계 burst 12 turns/s보다 계산량 자체가 충분히 작다는
|
||||
근거다. 그러나 DB-free in-memory profile이며 local CPU는 운영 후보 host와 다르다.
|
||||
@@ -541,19 +556,19 @@ Ryzen 7 5800X와 동급이라고 간주하지 않는다. 각 run은 idle 5초, o
|
||||
모두 성공했다. 3,607 dashboard 응답, 즉 10,821개 slice에서 전송한 `knownSource`가
|
||||
응답 source와 같았고 전부 payload loader 이전 `unchanged`로 반환됐다.
|
||||
|
||||
| 지표 | coverage 0 | coverage 1 | 변화 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| own dashboard 요청 | 1,347 | 2,454 | +82.2% |
|
||||
| mixed dashboard 요청 | 1,222 | 1,753 | +43.5% |
|
||||
| unchanged slice | 5,907 | 10,821 | +83.2% |
|
||||
| API CPU time / 약 35초 | 27.377 s | 15.505 s | -43.4% |
|
||||
| API MemoryPeak | 728.1 MB | 682.5 MB | -6.3% |
|
||||
| DB transaction delta | 52,367 | 34,237 | -34.6% |
|
||||
| DB tuple returned delta | 5,026,942 | 2,053,957 | -59.1% |
|
||||
| own mean / p50 | 1,107.4 / 1,113.0 ms | 214.2 / 4.7 ms | -80.7% / -99.6% |
|
||||
| own p95 / p99 | 1,460.6 / 1,564.7 ms | 1,566.1 / 1,869.2 ms | +7.2% / +19.5% |
|
||||
| mixed own mean / p50 | 498.0 / 493.1 ms | 76.5 / 4.5 ms | -84.6% / -99.1% |
|
||||
| mixed own p95 / p99 | 653.7 / 738.9 ms | 583.1 / 782.9 ms | -10.8% / +6.0% |
|
||||
| 지표 | coverage 0 | coverage 1 | 변화 |
|
||||
| ----------------------- | -------------------: | -------------------: | --------------: |
|
||||
| own dashboard 요청 | 1,347 | 2,454 | +82.2% |
|
||||
| mixed dashboard 요청 | 1,222 | 1,753 | +43.5% |
|
||||
| unchanged slice | 5,907 | 10,821 | +83.2% |
|
||||
| API CPU time / 약 35초 | 27.377 s | 15.505 s | -43.4% |
|
||||
| API MemoryPeak | 728.1 MB | 682.5 MB | -6.3% |
|
||||
| DB transaction delta | 52,367 | 34,237 | -34.6% |
|
||||
| DB tuple returned delta | 5,026,942 | 2,053,957 | -59.1% |
|
||||
| own mean / p50 | 1,107.4 / 1,113.0 ms | 214.2 / 4.7 ms | -80.7% / -99.6% |
|
||||
| own p95 / p99 | 1,460.6 / 1,564.7 ms | 1,566.1 / 1,869.2 ms | +7.2% / +19.5% |
|
||||
| mixed own mean / p50 | 498.0 / 493.1 ms | 76.5 / 4.5 ms | -84.6% / -99.1% |
|
||||
| mixed own p95 / p99 | 653.7 / 738.9 ms | 583.1 / 782.9 ms | -10.8% / +6.0% |
|
||||
|
||||
coverage 1 run에서 API CPU는 wall time 대비 약 한 core의 44.1%, 할당 4 CPU의 11.0%였다.
|
||||
global-only front/lobby/records p95는 각각 6.7/6.4/5.7ms였다. source-fast-path가 반복
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# 런타임 아키텍처
|
||||
|
||||
이 문서는 개발 경험자를 위한 상세 참고서입니다. 처음부터 모든 운영 설정을 외울
|
||||
필요는 없습니다. [전체 구조](./overview.md)와 [요청·턴·저장](../developer/request-turn-persistence.md)을
|
||||
읽고, 실행 문제에 해당하는 절을 찾아보세요. **프로세스**는 실행 중인 프로그램,
|
||||
**worker**는 특정 작업 담당, **daemon**은 계속 실행하며 일을 기다리는 프로그램입니다.
|
||||
아래 구성은 코드의 배포 정의이며 특정 운영 서버의 현재 실행 상태를 증명하지는 않습니다.
|
||||
|
||||
## 프로세스
|
||||
|
||||
| 프로세스 | 시작점 | 책임 |
|
||||
@@ -204,7 +210,7 @@ Mutation은 두 형태입니다.
|
||||
- API transaction으로 끝나는 mutation은 `executeInputEvent()`가
|
||||
`target=API` event를 만들고 결과와 event 상태를 같은 transaction에서
|
||||
commit합니다.
|
||||
- turn world가 필요한 mutation은 daemon transport가 `target=DAEMON`
|
||||
- turn world가 필요한 mutation은 daemon transport가 `target=ENGINE`
|
||||
event를 만들고 turn daemon의 처리 대상으로 전달합니다.
|
||||
|
||||
같은 `requestId`의 완료·처리 중 event는 중복 수락하지 않습니다. 실패 event는
|
||||
@@ -219,7 +225,8 @@ claim 가능한 상태에서 attempts를 증가시켜 재처리합니다.
|
||||
event와 resource snapshot을 읽습니다.
|
||||
3. scenario, map, unit set과 command profile을 적재합니다.
|
||||
4. action-module bundle, AI, command registry, 월간 event action을 조립합니다.
|
||||
5. `EngineStateManager`가 in-memory mutation과 transaction flush를 묶습니다.
|
||||
5. `EngineStateManager`가 메모리 상태의 snapshot·실패 복원을 맡고,
|
||||
`databaseHooks`가 실제 PostgreSQL transaction과 flush를 맡습니다.
|
||||
6. `TurnDaemonLifecycle`이 control queue와 schedule을 실행합니다.
|
||||
|
||||
`turn_daemon_lease`는 운영 WALL_TIME입니다. 모든 write와 active 비교는 DB
|
||||
@@ -237,7 +244,7 @@ lease/fencing 확인
|
||||
-> input_event claim
|
||||
-> in-memory command 또는 calendar action
|
||||
-> world dirty state와 side effect 수집
|
||||
-> EngineStateManager transaction
|
||||
-> EngineStateManager의 메모리 복원 경계 안에서 DB transaction
|
||||
-> world/general/nation/city/turn/log flush
|
||||
-> input_event result/status 갱신
|
||||
-> in-memory world checkpoint 갱신
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 시나리오 리소스 합성
|
||||
|
||||
시나리오는 게임 시작 조건과 사용할 지도·인물·병종·사건 등을 정하는 설정입니다.
|
||||
**합성**은 여러 설정 파일을 정해진 순서로 읽어 최종 설정 하나를 만드는 과정입니다.
|
||||
공통 규칙을 복사하지 않고 재사용하되, 어떤 값이 최종 적용되는지 명확해야 합니다.
|
||||
|
||||
`resources/scenario/scenario_*.json`은 공통 이벤트, 규칙과 아이템 구성을
|
||||
`extends`로 조합할 수 있습니다. 시나리오마다 같은 배열과 아이템 표를 복사하지
|
||||
말고, 독립적으로 켜고 끌 수 있는 기능은 `resources/scenario/extensions/`
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Time-domain inventory
|
||||
|
||||
이 문서는 시간을 필드별로 분류하는 개발자 참고표입니다. 먼저 [게임 시계](./game-clock.md)를
|
||||
읽으세요. `GAME_TIME`은 게임 진행, `WALL_TIME`은 현실의 만료·기록,
|
||||
`MONOTONIC_ELAPSED_TIME`은 프로세스 내부 경과시간입니다. 아래 `SHIFT`는 일정 이동,
|
||||
`KEEP`는 값 보존, `REBUILD`는 새 기준으로 다시 만드는 처리를 뜻합니다.
|
||||
|
||||
This document is the authoritative classification of persistent timestamps,
|
||||
deadlines, cooldowns, and process-local elapsed-time rules. Classification is
|
||||
per rule, not per table. A feature may record both a wall occurrence and a game
|
||||
|
||||
Reference in New Issue
Block a user