# 런타임 아키텍처 ## 프로세스 | 프로세스 | 시작점 | 책임 | | -------------------- | -------------------------------------------------------- | ------------------------------------------- | | gateway API | `app/gateway-api/src/server.ts` | 계정, session, profile, admin operation | | gateway orchestrator | `app/gateway-api/src/orchestrator/orchestratorServer.ts` | DB queue, build, PM2 reconciliation | | game API | `app/game-api/src/server.ts` | profile tRPC, SSE, worker transport | | turn daemon | `app/game-engine/src/turn/cli.ts` | schedule, command, 월간 lifecycle, DB flush | | battle worker | `app/game-api/src/battleSim/worker.ts` | 격리된 전투 시뮬레이션 | | auction worker | `app/game-api/src/auction/worker.ts` | 경매 timer와 낙찰 처리 | | tournament worker | `app/game-api/src/tournament/worker.ts` | 대회 진행과 결과 처리 | Gateway API와 game API는 기본적으로 `0.0.0.0`에 bind합니다. 실제 port와 prefix는 환경 변수와 배포 profile이 결정합니다. ## Gateway 실행 `resolveGatewayApiConfigFromEnv()`가 PostgreSQL schema, Redis prefix, session TTL, game-token secret, OAuth, local-account 정책과 orchestrator 설정을 검증합니다. Gateway API는 다음 저장 경계를 사용합니다. - `AppUser`, `SystemSetting`: 계정과 정책 - `GatewayProfile`: profile, scenario, port, 상태와 build 결과 - `GatewayOperation`: build/reset/open/close 등 실행 요청과 결과 - `GatewayRuntimeAction`: profile별 시간 가속·연기 요청, 부분 적용과 최종 결과 - Redis: gateway session, OAuth 임시 상태, flush channel Orchestrator는 `GatewayOperation`을 claim하고 source ref를 commit으로 해결합니다. `WorkspaceManager`가 commit별 worktree를 준비하고 build runner가 artifact를 만들며 `Pm2ProcessManager`가 profile process를 조정합니다. 재시작 시 DB 상태와 process 상태를 reconciliation합니다. 시간 가속·연기는 일반 profile meta log가 아니라 UUID가 있는 `GatewayRuntimeAction`으로 접수합니다. Profile별 `REQUESTED`/`PARTIAL`은 DB partial unique index로 한 건만 허용합니다. Turn daemon은 자신의 lease를 확인한 뒤 action ID로 결정적인 `InputEvent`를 만들고, world·전 장수·OPEN 경매·checkpoint를 같은 PostgreSQL transaction에서 이동합니다. Commit 뒤 경매 timer와 활성 토너먼트 시각을 Redis에 idempotent하게 투영합니다. Redis 단계가 실패하면 action은 `PARTIAL`과 backoff 상태로 남고 DB 시간은 다시 이동하지 않습니다. ## Game API 실행 `resolveGameApiConfigFromEnv()`가 `PROFILE`, `SCENARIO`, `GAME_PROFILE_NAME`, API·SSE·upload 경로와 worker timeout을 결정합니다. 기본 profile name은 `${profile}:${scenario}`입니다. Fastify server는 요청에서 game token을 검증해 `GameApiContext.auth`에 넣습니다. Router는 public procedure와 인증 procedure를 구분하고, 변경 대상 장수·국가·archive owner를 session actor와 DB에서 해석합니다. Mutation은 두 형태입니다. - API transaction으로 끝나는 mutation은 `executeInputEvent()`가 `target=API` event를 만들고 결과와 event 상태를 같은 transaction에서 commit합니다. - turn world가 필요한 mutation은 daemon transport가 `target=DAEMON` event를 만들고 turn daemon의 처리 대상으로 전달합니다. 같은 `requestId`의 완료·처리 중 event는 중복 수락하지 않습니다. 실패 event는 claim 가능한 상태에서 attempts를 증가시켜 재처리합니다. ## Turn daemon 조립 `createTurnDaemonRuntime()`은 다음 순서로 런타임을 구성합니다. 1. `DatabaseTurnDaemonLease`가 profile lease와 fencing token을 확보합니다. 2. `loadTurnWorldFromDatabase()`가 world, 장수, 국가, 도시, 외교, 예약 턴, 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를 묶습니다. 6. `TurnDaemonLifecycle`이 control queue와 schedule을 실행합니다. Lifecycle은 가장 빠른 장수 턴과 다음 tick 중 앞선 시각을 선택합니다. pause gate, 수동 run, shutdown과 budget을 같은 loop에서 처리합니다. ## 한 번의 실행과 저장 ```text lease/fencing 확인 -> input_event claim -> in-memory command 또는 calendar action -> world dirty state와 side effect 수집 -> EngineStateManager transaction -> world/general/nation/city/turn/log flush -> input_event result/status 갱신 -> checkpoint 갱신 -> commit -> realtime 알림 ``` Transaction 실패 시 in-memory snapshot을 복원하고 event는 재시도 가능한 실패 상태로 남깁니다. Lease 소유권을 잃은 daemon은 flush를 확정하지 않습니다. 예약 턴은 revision/CAS와 lease를 사용합니다. API의 편집과 daemon의 실행이 경합해도 오래된 revision이 새 queue를 덮어쓰지 않게 합니다. ## 월간 경계 Calendar handler는 turn time이 월 경계를 지날 때 scenario event table의 action을 순서대로 실행합니다. Core event, 수입, 국가 등급, NPC, 이민족, 도시 공급, 전쟁 수입, 특기, 유니크·유산, 베팅, 통일·연감 처리는 `app/game-engine/src/turn/monthly*.ts`, `calendarHandlers.ts`, `unificationHandler.ts`, `yearbookHandler.ts`에서 조립됩니다. Action 순서, RNG 소비와 persistence 순서는 ref 호환 계약입니다. 새 handler는 event catalog, in-memory state, dirty marking, flush와 reload 검증까지 연결합니다. ## 장애 경계 - PostgreSQL은 gameplay mutation과 daemon 소유권의 기준입니다. - Redis pub/sub, SSE와 flush notification은 commit 이후 best-effort입니다. - Worker timeout은 요청 실패로 반환하며 DB commit 여부를 별도로 확인합니다. - API·daemon process 재시작은 `InputEvent`, lease, checkpoint와 operation 상태에서 이어집니다. - 운영 process와 외부 Caddy 상태는 local build·mock E2E로 증명되지 않습니다. ## Build와 배포 Gateway operation은 source commit, worktree, build artifact와 process를 연결합니다. `tools/build-scripts/build-server.mjs`는 profile resource 복사만 담당합니다. 외부 공개 경로는 `/gateway/`, `/che/`, `/hwe/`입니다. frontend base, tRPC, SSE, upload와 direct navigation은 해당 prefix를 유지합니다. `/image/*`는 Caddy의 별도 파일 시스템 경로입니다. Game과 gateway가 같은 PostgreSQL database/schema를 사용할 때 migration은 반드시 game 다음 gateway 순서로 적용합니다. 두 migration history는 하나의 `_prisma_migrations`를 공유하므로 새 migration directory 이름은 양쪽을 통틀어 고유해야 합니다. 과거 양쪽의 `20260727000000_add_legacy_migration_archive` 이름 충돌은 checksum을 바꾸지 않고 별도의 idempotent reconciliation migration으로 보정합니다.