독자 수준별 구조 문서와 게이머 입문·전술 안내를 재정비한다

This commit is contained in:
2026-09-29 08:53:30 +00:00
parent 71155186b7
commit 08c3d0e936
34 changed files with 1175 additions and 271 deletions
+7
View File
@@ -5,6 +5,13 @@
비교하고, 명시적인 신규 기능·밸런스·UX는 Core 계약으로 검증합니다. 비교하고, 명시적인 신규 기능·밸런스·UX는 Core 계약으로 검증합니다.
작업 규칙과 분야별 지침의 시작점은 [AGENTS.md](AGENTS.md)입니다. 작업 규칙과 분야별 지침의 시작점은 [AGENTS.md](AGENTS.md)입니다.
## 독자별 시작점
- 게임이 처음인 플레이어: [삼국지 모의전투 첫 안내](docs/user/index.md)
- 프로그래밍 기초만 아는 독자: [화면·서버·저장부터 배우기](docs/developer/first-steps.md)
- 개발 경험이 있는 독자: [요청·실행·운영의 구조 읽기](docs/developer/system-walkthrough.md)
- 전체 문서 탐색: [핸드북](docs/index.md), [게시판 용어 사전](docs/user/glossary.md)
## 저장소 구성 ## 저장소 구성
| 경로 | 책임 | | 경로 | 책임 |
+26 -1
View File
@@ -17,11 +17,30 @@ export default defineConfig({
{ text: '릴리스 운영', link: '/release-operations' }, { text: '릴리스 운영', link: '/release-operations' },
], ],
sidebar: { sidebar: {
'/architecture/': [
{
text: '구조와 학습 경로',
items: [
{ text: '독자별 읽기 순서', link: '/developer/' },
{ text: '아키텍처 개요', link: '/architecture/overview' },
{ text: '런타임', link: '/architecture/runtime' },
{ text: '패키지 경계', link: '/architecture/package-boundaries' },
{ text: '요청·턴·저장', link: '/developer/request-turn-persistence' },
{ text: '게임 시계', link: '/architecture/game-clock' },
{ text: '시나리오 합성', link: '/architecture/scenario-composition' },
{ text: '행동 모듈', link: '/architecture/action-module-protocol' },
{ text: '실시간 변경 알림', link: '/architecture/realtime-change-journal' },
],
},
],
'/developer/': [ '/developer/': [
{ {
text: '개발자 핸드북', text: '개발자 핸드북',
items: [ items: [
{ text: '시작하기', link: '/developer/' }, { text: '독자별 읽기 순서', link: '/developer/' },
{ text: '기초 구조 안내', link: '/developer/first-steps' },
{ text: '경험자를 위한 시스템 읽기', link: '/developer/system-walkthrough' },
{ text: '구조 문서 찾아보기', link: '/developer/reference-map' },
{ text: '아키텍처 개요', link: '/architecture/overview' }, { text: '아키텍처 개요', link: '/architecture/overview' },
{ text: '런타임 아키텍처', link: '/architecture/runtime' }, { text: '런타임 아키텍처', link: '/architecture/runtime' },
{ text: '관리자 콘솔', link: '/admin-console' }, { text: '관리자 콘솔', link: '/admin-console' },
@@ -37,7 +56,13 @@ export default defineConfig({
text: '플레이어 가이드', text: '플레이어 가이드',
items: [ items: [
{ text: '시작하기', link: '/user/' }, { text: '시작하기', link: '/user/' },
{ text: '장수와 도시, 내정', link: '/user/general-and-city' },
{ text: '시간과 턴', link: '/user/time-and-turns' }, { text: '시간과 턴', link: '/user/time-and-turns' },
{ text: '전쟁과 예턴 조합', link: '/user/war-and-orders' },
{ text: '보급·정찰·땅따', link: '/user/map-and-supply' },
{ text: '국가 재정과 외교', link: '/user/economy-and-diplomacy' },
{ text: '용어 사전', link: '/user/glossary' },
{ text: '자료와 확인 범위', link: '/user/sources' },
{ text: '커맨드와 실행 시기', link: '/user/commands-and-timing' }, { text: '커맨드와 실행 시기', link: '/user/commands-and-timing' },
{ text: '커맨드 전체 목록', link: '/user/command-catalog.generated' }, { text: '커맨드 전체 목록', link: '/user/command-catalog.generated' },
{ text: '국가 운영과 주요 기능', link: '/user/nation-and-features' }, { text: '국가 운영과 주요 기능', link: '/user/nation-and-features' },
+9 -3
View File
@@ -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 재실행·복구 계약 # API input-event 재실행·복구 계약
요청을 처리했지만 응답을 받지 못한 사용자가 다시 시도할 때, 상태를 두 번 바꾸지
않으면서 원래 결과를 돌려주기 위한 계약입니다. 여기서 **replay**는 저장한 API
응답의 재사용이며 NPC 판단이나 게임 전체의 재실행과는 다릅니다.
전체 흐름은 [요청·턴·저장](../developer/request-turn-persistence.md)에 있습니다.
## 목적과 범위 ## 목적과 범위
game-api mutation은 HTTP `Idempotency-Key`를 profile·인증 actor와 함께 scope한 base ID, 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 `BattleSimulatorView.vue`는 화면을 열 때 `battle.getSimulatorContext`에서 form
@@ -1,5 +1,10 @@
# game-api 턴 데몬 procedure/transaction inventory # 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를 거쳐 `app/game-api/src/router/**`의 mutation에서 직접 또는 router 전용 helper를 거쳐
@@ -37,7 +42,7 @@ selection-pool create/reselect는 client request ID가 있을 때
## ENGINE procedure로 전환된 route ## ENGINE procedure로 전환된 route
| route | 현재 procedure / API-side 작업 | ENGINE 소유 근거 | | 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`) | | `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`) | | `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`) | | `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`) |
@@ -53,7 +58,7 @@ selection-pool create/reselect는 client request ID가 있을 때
## 혼합 또는 validation 이관이 먼저 필요한 route ## 혼합 또는 validation 이관이 먼저 필요한 route
| route | 현재 procedure / outer transaction | 보류 근거 | | 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로 보지 않는다. | | `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이 필요하다. | | `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이 필요하다. |
@@ -78,7 +83,7 @@ mutation lock → API input-event/DB clock fence → 상태 변경 → commit
## 기존에 API outer transaction이 없던 ENGINE route ## 기존에 API outer transaction이 없던 ENGINE route
| 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`) | | `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`) | | `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`) |
@@ -1,5 +1,11 @@
# Game clock reconciliation # Game clock reconciliation
게임을 멈춘 뒤 다시 시작할 때 여러 기능의 남은 시간과 순서를 함께 맞추는 계약입니다.
**재정렬(reconciliation)**은 화면 날짜만 고치는 일이 아니라 DB 일정·worker 기한·
메모리 상태를 같은 세대로 전환하는 과정입니다. 개념은 [게임 시계](./game-clock.md),
실패 대응은 [복구 안내](../developer/game-clock-recovery.md)를 먼저 읽으세요.
아래 `EXACT`는 역사적 정책이고 현재 maintenance·장애 복구는 `RECOVER_TURNS`입니다.
## Product contract ## Product contract
Gameplay time is an integer `GameTick`; one turn is permanently `36,000,000` Gameplay time is an integer `GameTick`; one turn is permanently `36,000,000`
+18 -8
View File
@@ -1,5 +1,9 @@
# 게임 시계 # 게임 시계
예약된 행동까지 얼마나 남았는지와 로그인·소유권이 현실에서 언제 만료되는지는
다른 질문입니다. 그래서 게임을 멈출 때 모든 시간을 한꺼번에 멈추지 않습니다.
이 문서는 그 구분을 설명하고, 상세 필드 목록은 아래 시간 도메인 문서로 연결합니다.
시간 규칙은 `GAME_TIME`, `WALL_TIME`, `MONOTONIC_ELAPSED_TIME`로 시간 규칙은 `GAME_TIME`, `WALL_TIME`, `MONOTONIC_ELAPSED_TIME`로
나뉩니다. 게임 진행의 권위는 `world_state.clock_tick`, 영속 wall 나뉩니다. 게임 진행의 권위는 `world_state.clock_tick`, 영속 wall
판정의 권위는 PostgreSQL UTC 시계, 프로세스 내부 경과시간의 권위는 판정의 권위는 PostgreSQL UTC 시계, 프로세스 내부 경과시간의 권위는
@@ -67,14 +71,20 @@ column을 9시간 미래로 쓰지 않습니다.
## 중단 후 재개 ## 중단 후 재개
realtime daemon은 재개할 때 Ref `checkDelay()`와 같은 한도를 적용합니다. 현재 maintenance·장애 복구는 `RECOVER_TURNS` 정책을 사용합니다. 전체 중단이
밀린 완전 턴 수가 턴 간격 20분 이상이면 1턴, 10분 이상이면 3턴, 그보다 현실 시간 10분 이내면 기존 실행 순서와 budget으로 밀린 턴을 처리합니다.
짧으면 6턴을 초과할 때 장기 중단으로 봅니다. 한도 이내의 짧은 중단은 턴을 그보다 길면 밀린 시간 중 완전한 12턴 묶음을 일정 이동으로 건너뛰고, 나머지
순서대로 실행해 따라잡고, 한도를 넘으면 밀린 완전 턴만큼 `last_turn_tick`, 지연은 월 경계에 맞춘 복구 구간에서 두 배 속도로 실행해 정상 시간표에 합류합니다.
전 장수의 `turn_tick`, 미완료 경매의 `close_tick`과 각각의 DateTime 투영값을 설정된 기본 턴 간격 자체를 절반으로 변경하지 않습니다.
한 transaction에서 옮깁니다. 이 보정은 명령이나 월 이벤트를 실행하지 않으므로
건너뛴 기간의 RNG를 소비하지 않습니다. 이미 처리 중 anchor 갱신으로 표시 건너뛴 구간에는 게임 행동·월 이벤트를 실행하지 않으며, 따라잡는 구간에는 실제
시각이 늦어진 상태도 현재 wall time에 맞추되 game tick은 되감지 않습니다. 행동을 순서대로 실행합니다. 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 작업을 사용합니다. 운영자가 명시적으로 일정을 지연하거나 가속하려면 Gateway 작업을 사용합니다.
이 작업은 `clock_base_time`과 DateTime 투영값을 같이 이동하고, game tick 및 이 작업은 `clock_base_time`과 DateTime 투영값을 같이 이동하고, game tick 및
+20 -1
View File
@@ -1,5 +1,10 @@
# core2026 아키텍처 # core2026 아키텍처
이 문서는 프로그램들의 책임을 한눈에 보는 지도입니다. 웹 서버가 처음이라면
[기초 구조 안내](../developer/first-steps.md), 개발 경험이 있다면
[시스템 읽기](../developer/system-walkthrough.md)를 먼저 읽어도 좋습니다.
세부 운영 절차는 [런타임](./runtime.md)에 있습니다.
## 시스템 경계 ## 시스템 경계
`core2026`은 계정·profile 운영을 담당하는 gateway와 profile별 게임 런타임을 `core2026`은 계정·profile 운영을 담당하는 gateway와 profile별 게임 런타임을
@@ -35,6 +40,10 @@ PostgreSQL 상태와 결합해 서버에서 결정합니다.
- `app/gateway-api/src/orchestrator/`는 DB operation queue, commit별 worktree, - `app/gateway-api/src/orchestrator/`는 DB operation queue, commit별 worktree,
build, PM2 process와 예약 상태를 조정합니다. build, PM2 process와 예약 상태를 조정합니다.
Gateway 자체를 업데이트하는 `app/release-controller`는 Gateway 밖에서 실행합니다.
게임 profile 작업을 담당하는 orchestrator와 Gateway 전체 릴리스 담당을 나누어,
업데이트 대상 프로세스가 자신의 종료 이후 단계까지 직접 책임지지 않도록 합니다.
### Game ### Game
- `app/game-frontend`는 profile base path에서 실행하는 Vue SPA입니다. - `app/game-frontend`는 profile base path에서 실행하는 Vue SPA입니다.
@@ -70,6 +79,14 @@ Command는 `GeneralActionDefinition` 또는 국가 command module로 args,
constraint, turn metadata, 결과와 로그를 선언합니다. 런타임 context와 constraint, turn metadata, 결과와 로그를 선언합니다. 런타임 context와
persistence는 `app/game-engine`이 제공합니다. persistence는 `app/game-engine`이 제공합니다.
### 자주 나오는 용어
- **registry**: 이름으로 사용할 명령·효과를 찾아 주는 등록 목록입니다.
- **loader**: 파일이나 DB 값을 읽어 실행에 필요한 형태로 만드는 코드입니다.
- **context**: 한 행동을 계산할 때 필요한 장수·도시·시각·난수 등의 입력 묶음입니다.
- **hook·trigger**: 정해진 계산 또는 사건 지점에 연결하는 추가 효과입니다.
- **persistence**: 재시작해도 남도록 저장하는 일입니다.
### `packages/infra` ### `packages/infra`
`prisma/gateway.prisma`와 `prisma/game.prisma`가 영속 schema의 기준입니다. `prisma/gateway.prisma`와 `prisma/game.prisma`가 영속 schema의 기준입니다.
@@ -99,7 +116,9 @@ DB commit의 대체 조건으로 사용하지 않습니다.
## 시나리오와 profile ## 시나리오와 profile
`profile`은 규칙·자산 계열이고 `scenario`는 게임 초기 데이터입니다. 운영 화면의 프로필은 독립적으로 실행·배포·진행 상태를 관리하는 게임 대상입니다.
설정에서 `profile`은 규칙·자산 계열을, `scenario`는 선택한 시나리오를 나타냅니다.
서로 다른 곳에서 쓰는 같은 이름을 하나의 값으로 혼동하지 마세요.
런타임 식별자는 `${profile}:${scenario}`입니다. Scenario JSON은 런타임 식별자는 `${profile}:${scenario}`입니다. Scenario JSON은
`resources/scenario`에서 합성되고 zod parser를 거쳐 seeder와 runtime에 `resources/scenario`에서 합성되고 zod parser를 거쳐 seeder와 runtime에
전달됩니다. map, unit set, turn-command profile도 resource loader를 통해 전달됩니다. map, unit set, turn-command profile도 resource loader를 통해
+15 -13
View File
@@ -2,21 +2,23 @@
## 의존 방향 ## 의존 방향
제품 소스의 의존 방향은 다음과 같습니다. 패키지는 함께 재사용할 코드의 묶음입니다. **의존 방향**은 어느 묶음이 다른
묶음의 코드를 가져오는지 뜻하며, 서버 사이의 네트워크 통신 방향과는 다릅니다.
아래는 `tools/check-package-boundaries.mjs`가 허용하는 workspace 의존입니다.
허용 목록은 모든 파일이 실제로 전부 import한다는 뜻은 아닙니다.
```text | 가져오는 쪽 | 가져올 수 있는 workspace 코드 |
packages/common | -------------------- | ---------------------------------------------------------- |
↑ | common | 다른 workspace package 없음 |
packages/logic ← packages/infra | logic | common |
↑ ↑ | infra | common, logic |
└──── app/game-engine ────┐ | game-engine | common, logic, infra |
↑ │ | game-api·gateway-api | common, logic, infra, game-engine의 공개 subpath |
app/game-api app/gateway-api | release-controller | gateway-api, infra |
↑ ↑ | game-frontend | common, logic의 브라우저용 값; game-api·gateway-api의 타입 |
game-frontend gateway-frontend | gateway-frontend | common의 브라우저용 값; game-api·gateway-api의 타입 |
```
화살표의 시작점이 끝점을 import합니다. `packages/logic`은 DB, Redis, 파일, `packages/logic`은 DB, Redis, 파일,
네트워크, 환경 변수와 stdout을 직접 사용하지 않습니다. 런타임 관찰이 필요한 네트워크, 환경 변수와 stdout을 직접 사용하지 않습니다. 런타임 관찰이 필요한
경우 `packages/logic/src/ports/`에 포트를 선언하고 app 계층에서 구현을 경우 `packages/logic/src/ports/`에 포트를 선언하고 app 계층에서 구현을
주입합니다. Prisma 생성 타입과 connector는 `packages/infra`가 소유하며, 주입합니다. Prisma 생성 타입과 connector는 `packages/infra`가 소유하며,
+24 -9
View File
@@ -1,5 +1,20 @@
# 실시간 read-model change journal과 revision-first 조회 설계 # 실시간 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 메인 화면의 실시간 갱신이 실제 화면 변화가 없는 경우에도 viewer별 PostgreSQL
@@ -19,9 +34,9 @@ read model을 다시 구성한 뒤 `unchanged`를 판정하는 비용을 제거
추산이 아니라 아래 workload를 실제 PostgreSQL·Redis·HTTP/SSE 경계에서 실행한 추산이 아니라 아래 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`, 수명의 in-memory baseline과 비교한다. `databaseHooks`가 `content`, `map`,
`contacts`, `frontStatus`, `lobby` canonical projection을 나누어 비교한 뒤 `contacts`, `frontStatus`, `lobby` canonical projection을 나누어 비교한 뒤
`RealtimeReadModelChanges`를 만든다. 이 단계는 dirty entity만 직렬화하며 일반 `RealtimeReadModelChanges`를 만든다. 이 단계는 dirty entity만 직렬화하며 일반
@@ -46,7 +61,7 @@ readModelInvalidated
보통 소속 장수의 context-only 자동 갱신은 access gate를 포함해 약 13 SQL이고, 보통 소속 장수의 context-only 자동 갱신은 access gate를 포함해 약 13 SQL이고,
command table까지 포함하면 약 21 SQL이다. 300 viewer가 global 변화 burst를 1초 command table까지 포함하면 약 21 SQL이다. 300 viewer가 global 변화 burst를 1초
간격으로 받으면 bundle만 이론상 약 3,900~6,300 statement/s까지 커질 수 있다. 간격으로 받으면 bundle만 이론상 약 3,900~6,300 statement/s까지 커질 수 있다.
이는 실제 운영 측정값이 아니라 현재 호출 그래프의 상한식이며, 구현 뒤 실제 이는 실제 운영 측정값이 아니라 설계 당시 호출 그래프의 상한식이며, 구현 뒤 실제
statement rate로 대체한다. statement rate로 대체한다.
## 보존할 계약 ## 보존할 계약
@@ -163,7 +178,7 @@ statement 수와 row lock 시간을 제한한다. 없는 key의 revision은 0으
초기 domain은 다음과 같다. 초기 domain은 다음과 같다.
| domain | entity ID | 의미 | | domain | entity ID | 의미 |
| --- | ---: | --- | | ------------------ | ---------: | ----------------------------------------------------------------------------------- |
| `general.content` | general ID | 현재 장수 context/command/board dependency | | `general.content` | general ID | 현재 장수 context/command/board dependency |
| `city.content` | city ID | 현재 도시 context/command dependency | | `city.content` | city ID | 현재 도시 context/command dependency |
| `nation.content` | nation ID | 현재 국가 context/command/board dependency | | `nation.content` | nation ID | 현재 국가 context/command/board dependency |
@@ -377,7 +392,7 @@ BroadcastChannel 계약을 유지한다. 변경 ID set/boolean은 drop하지 않
초기 cadence 목표는 다음과 같다. 초기 cadence 목표는 다음과 같다.
| 종류 | 최대 시작 빈도 | 이유 | | 종류 | 최대 시작 빈도 | 이유 |
| --- | ---: | --- | | ------------------------------ | -------------: | -------------------------------- |
| 자기 context/commands/board | 1초 1회 | 자기 명령 결과의 빠른 반영 | | 자기 context/commands/board | 1초 1회 | 자기 명령 결과의 빠른 반영 |
| records/front status | 2초 1회 | global burst 합치기 | | records/front status | 2초 1회 | global burst 합치기 |
| map/lobby/tournament/betting | 5초 1회 | 300 viewer의 shared fan-out 제한 | | map/lobby/tournament/betting | 5초 1회 | 300 viewer의 shared fan-out 제한 |
@@ -445,7 +460,7 @@ visible 복귀는 leader가 fresh 3-slice snapshot 한 번을 읽어 pub/sub gap
### 2026-08-17 구현 상태 ### 2026-08-17 구현 상태
| Phase | 상태 | 현재 근거 | | Phase | 상태 | 현재 근거 |
| --- | --- | --- | | ----- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A | 완료 | all-false access gate, frontend 강제 context 제거, Chromium realtime trace | | 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 | | 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 | | C | 완료 | dashboard revision-first, auth/global dependency, durable map cache, 모든 tournament Redis writer 원자화, stage-only main realtime invalidation, coverage v1 activation/rollback integration |
@@ -473,7 +488,7 @@ API와 process 경합을 제외하므로 production 수용 근거로 단독 사
### workload ### workload
| ID | workload | 필수 관찰값 | | ID | workload | 필수 관찰값 |
| --- | --- | --- | | --- | --------------------------------------------------- | -------------------------------------------------------------------- |
| E1 | 5분 턴, 900 NPC 및 총 1,200장수 DB-free 고정 seed | turns/actions/s, command p95/p99, memory high-water, 최종 state hash | | 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 | | 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 | | A1 | 300 SSE idle 30분 | 연결 성공/유지율, ping, runtime RSS, event-loop lag |
@@ -516,7 +531,7 @@ API와 process 경합을 제외하므로 production 수용 근거로 단독 사
`d14a5f451385095bb56f9459928f100bf81864178bac9861686e06001a7f02a0`이었다. `d14a5f451385095bb56f9459928f100bf81864178bac9861686e06001a7f02a0`이었다.
| 관찰값 | 결과 | | 관찰값 | 결과 |
| --- | ---: | | ---------------------- | ------------------------: |
| 처리량 | 1,283.986 general turns/s | | 처리량 | 1,283.986 general turns/s |
| general turn p95 / p99 | 2.793 / 3.393 ms | | general turn p95 / p99 | 2.793 / 3.393 ms |
| month wall p95 | 934.590 ms | | month wall p95 | 934.590 ms |
@@ -542,7 +557,7 @@ Ryzen 7 5800X와 동급이라고 간주하지 않는다. 각 run은 idle 5초, o
응답 source와 같았고 전부 payload loader 이전 `unchanged`로 반환됐다. 응답 source와 같았고 전부 payload loader 이전 `unchanged`로 반환됐다.
| 지표 | coverage 0 | coverage 1 | 변화 | | 지표 | coverage 0 | coverage 1 | 변화 |
| --- | ---: | ---: | ---: | | ----------------------- | -------------------: | -------------------: | --------------: |
| own dashboard 요청 | 1,347 | 2,454 | +82.2% | | own dashboard 요청 | 1,347 | 2,454 | +82.2% |
| mixed dashboard 요청 | 1,222 | 1,753 | +43.5% | | mixed dashboard 요청 | 1,222 | 1,753 | +43.5% |
| unchanged slice | 5,907 | 10,821 | +83.2% | | unchanged slice | 5,907 | 10,821 | +83.2% |
+10 -3
View File
@@ -1,5 +1,11 @@
# 런타임 아키텍처 # 런타임 아키텍처
이 문서는 개발 경험자를 위한 상세 참고서입니다. 처음부터 모든 운영 설정을 외울
필요는 없습니다. [전체 구조](./overview.md)와 [요청·턴·저장](../developer/request-turn-persistence.md)을
읽고, 실행 문제에 해당하는 절을 찾아보세요. **프로세스**는 실행 중인 프로그램,
**worker**는 특정 작업 담당, **daemon**은 계속 실행하며 일을 기다리는 프로그램입니다.
아래 구성은 코드의 배포 정의이며 특정 운영 서버의 현재 실행 상태를 증명하지는 않습니다.
## 프로세스 ## 프로세스
| 프로세스 | 시작점 | 책임 | | 프로세스 | 시작점 | 책임 |
@@ -204,7 +210,7 @@ Mutation은 두 형태입니다.
- API transaction으로 끝나는 mutation은 `executeInputEvent()`가 - API transaction으로 끝나는 mutation은 `executeInputEvent()`가
`target=API` event를 만들고 결과와 event 상태를 같은 transaction에서 `target=API` event를 만들고 결과와 event 상태를 같은 transaction에서
commit합니다. commit합니다.
- turn world가 필요한 mutation은 daemon transport가 `target=DAEMON` - turn world가 필요한 mutation은 daemon transport가 `target=ENGINE`
event를 만들고 turn daemon의 처리 대상으로 전달합니다. event를 만들고 turn daemon의 처리 대상으로 전달합니다.
같은 `requestId`의 완료·처리 중 event는 중복 수락하지 않습니다. 실패 event는 같은 `requestId`의 완료·처리 중 event는 중복 수락하지 않습니다. 실패 event는
@@ -219,7 +225,8 @@ claim 가능한 상태에서 attempts를 증가시켜 재처리합니다.
event와 resource snapshot을 읽습니다. event와 resource snapshot을 읽습니다.
3. scenario, map, unit set과 command profile을 적재합니다. 3. scenario, map, unit set과 command profile을 적재합니다.
4. action-module bundle, AI, command registry, 월간 event action을 조립합니다. 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을 실행합니다. 6. `TurnDaemonLifecycle`이 control queue와 schedule을 실행합니다.
`turn_daemon_lease`는 운영 WALL_TIME입니다. 모든 write와 active 비교는 DB `turn_daemon_lease`는 운영 WALL_TIME입니다. 모든 write와 active 비교는 DB
@@ -237,7 +244,7 @@ lease/fencing 확인
-> input_event claim -> input_event claim
-> in-memory command 또는 calendar action -> in-memory command 또는 calendar action
-> world dirty state와 side effect 수집 -> world dirty state와 side effect 수집
-> EngineStateManager transaction -> EngineStateManager의 메모리 복원 경계 안에서 DB transaction
-> world/general/nation/city/turn/log flush -> world/general/nation/city/turn/log flush
-> input_event result/status 갱신 -> input_event result/status 갱신
-> in-memory world checkpoint 갱신 -> in-memory world checkpoint 갱신
@@ -1,5 +1,9 @@
# 시나리오 리소스 합성 # 시나리오 리소스 합성
시나리오는 게임 시작 조건과 사용할 지도·인물·병종·사건 등을 정하는 설정입니다.
**합성**은 여러 설정 파일을 정해진 순서로 읽어 최종 설정 하나를 만드는 과정입니다.
공통 규칙을 복사하지 않고 재사용하되, 어떤 값이 최종 적용되는지 명확해야 합니다.
`resources/scenario/scenario_*.json`은 공통 이벤트, 규칙과 아이템 구성을 `resources/scenario/scenario_*.json`은 공통 이벤트, 규칙과 아이템 구성을
`extends`로 조합할 수 있습니다. 시나리오마다 같은 배열과 아이템 표를 복사하지 `extends`로 조합할 수 있습니다. 시나리오마다 같은 배열과 아이템 표를 복사하지
말고, 독립적으로 켜고 끌 수 있는 기능은 `resources/scenario/extensions/` 말고, 독립적으로 켜고 끌 수 있는 기능은 `resources/scenario/extensions/`
+5
View File
@@ -1,5 +1,10 @@
# Time-domain inventory # 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, This document is the authoritative classification of persistent timestamps,
deadlines, cooldowns, and process-local elapsed-time rules. Classification is 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 per rule, not per table. A feature may record both a wall occurrence and a game
+6 -1
View File
@@ -1,5 +1,9 @@
# 파일 지도 # 파일 지도
먼저 증상이 보이는 화면이나 동작 하나를 고르고, 아래 시작점에서 호출을 따라가세요.
폴더 이름만으로 저장 책임이나 권한을 추측하지 않습니다. 읽는 순서는
[기초 안내](./first-steps.md)와 [경험자 안내](./system-walkthrough.md)에 있습니다.
## 최상위 ## 최상위
```text ```text
@@ -9,7 +13,8 @@ core2026/
│ ├─ gateway-api/ │ ├─ gateway-api/
│ ├─ game-frontend/ │ ├─ game-frontend/
│ ├─ game-api/ │ ├─ game-api/
│ └─ game-engine/ │ ├─ game-engine/
│ └─ release-controller/
├─ packages/ ├─ packages/
│ ├─ common/ │ ├─ common/
│ ├─ logic/ │ ├─ logic/
+27 -9
View File
@@ -1,16 +1,33 @@
# 도메인과 조립 지점 # 도메인과 조립 지점
**도메인**은 장수·도시·전투처럼 이 게임이 다루는 개념과 규칙입니다. 이 문서는
객체 지향 문법을 설명하기보다 “규칙을 누가 계산하고 결과를 누가 적용하는가”를
따라갑니다. [기초 안내](./first-steps.md)의 모병 예시를 떠올리면 좋습니다.
```text
저장된 장수·도시 → loader → 메모리 세계
예약한 명령 → 입력 해석 → 조건 검사 → 규칙 계산 → 변경 결과
변경 결과 → 메모리 세계에 적용 → DB 저장 또는 실패 시 복원
```
## World entity ## World entity
`packages/logic/src/domain/entities.ts`와 `world/types.ts`가 장수, 국가, 도시, `packages/logic/src/domain/entities.ts`와 `world/types.ts`가 장수, 국가, 도시,
부대, 외교와 trigger state의 런타임 타입을 정의합니다. Prisma row는 부대, 외교와 trigger state의 런타임 타입을 정의합니다. Prisma row는
`app/game-engine/src/turn/worldLoader.ts`가 이 타입으로 변환합니다. `app/game-engine/src/turn/worldLoader.ts`가 이 타입으로 변환합니다.
`InMemoryTurnWorld`가 조회와 mutation을 제공하고 `EngineStateManager`가 `InMemoryTurnWorld`가 조회와 mutation을 제공하고 `EngineStateManager`가
transaction snapshot과 dirty state를 관리합니다. 메모리 snapshot과 실패 복원을 관리합니다. 실제 DB transaction은
`databaseHooks.ts`의 책임입니다. **dirty state**는 마지막 저장 이후 바뀌어 다시
저장해야 하는 상태를 뜻합니다.
## Command ## Command
장수 command는 `GeneralActionDefinition`으로 다음 계약을 가집니다. 명령(command)은 “모병” 같은 행동 한 종류입니다. 입력(args)은 병종·수량처럼
그 행동에 필요한 값입니다. **constraint**는 실행에 필요한 조건이고,
**state patch**는 병력·자원 등 바꿀 값의 묶음입니다. 실행 결과에는 patch뿐 아니라
플레이어에게 보일 로그와 후속 효과도 포함됩니다.
장수 command는 definition·command spec·resolver를 통해 다음 계약을 연결합니다.
- key와 사용자 표시 이름 - key와 사용자 표시 이름
- raw args parser - raw args parser
@@ -38,25 +55,26 @@ turn 소비와 side effect 계약을 보존합니다.
priority trigger와 의미 event는 각각 다른 interface를 사용합니다. priority trigger와 의미 event는 각각 다른 interface를 사용합니다.
[행동 모듈 프로토콜](../architecture/action-module-protocol.md)을 따라 주세요. [행동 모듈 프로토콜](../architecture/action-module-protocol.md)을 따라 주세요.
## 주요 클래스 ## 주요 클래스와 함수
| 클래스·함수 | 책임 | | 클래스·함수 | 책임 |
| ------------------------- | -------------------------------------------------- | | ----------------------------- | -------------------------------------------------- |
| `GatewayOrchestrator` | profile operation과 process reconciliation | | `GatewayOrchestrator` | profile operation과 process reconciliation |
| `createGameApiServer` | game transport, context, router와 worker lifecycle | | `createGameApiServer` | game transport, context, router와 worker lifecycle |
| `DatabaseTurnDaemonLease` | profile별 lease, heartbeat와 fencing | | `DatabaseTurnDaemonLease` | profile별 lease, heartbeat와 fencing |
| `TurnDaemonLifecycle` | schedule, pause/resume/run/shutdown loop | | `TurnDaemonLifecycle` | schedule, pause/resume/run/shutdown loop |
| `InMemoryTurnWorld` | turn 실행 중 world state와 dirty tracking | | `InMemoryTurnWorld` | turn 실행 중 world state와 dirty tracking |
| `EngineStateManager` | snapshot, transaction flush와 rollback restore | | `EngineStateManager` | 메모리 snapshot, 실패 시 restore |
| `ReservedTurnHandler` | revision·lease 기반 예약 명령 claim과 실행 | | `createReservedTurnHandler()` | revision·lease 기반 예약 명령 claim과 실행 |
| `GeneralActionPipeline` | action module 계산·trigger·event 실행 | | `GeneralActionPipeline` | action module 계산·trigger·event 실행 |
| `WarEngine` | 전투 phase, RNG, 상태와 log 결과 | | `resolveWarBattle()` | 전투 phase, RNG, 상태와 log 결과 |
## 명령 추가 ## 명령 추가
1. ref command의 예약·실행 constraint, args, RNG, log와 DB mutation을 찾습니다. 1. 계승 명령이면 Ref의 예약·실행 조건, 입력, RNG, 로그와 DB 변경을 찾습니다.
Core 전용 명령이면 요구사항과 의도한 결과를 먼저 정합니다.
2. command definition과 필요한 domain helper를 추가합니다. 2. command definition과 필요한 domain helper를 추가합니다.
3. engine registry, profile resource와 frontend args UI를 연결합니다. 3. engine registry, profile resource와 frontend args UI를 연결합니다.
4. state patch와 dirty field가 flush·reload되는지 확인합니다. 4. state patch와 dirty field가 flush·reload되는지 확인합니다.
5. 정상·실패·경계 fixed-seed test와 ref 차등 fixture를 추가합니다. 5. 정상·실패·경계 fixed-seed test를 추가하고, 계승 계약은 Ref 차등 fixture로 확인합니다.
6. 생성 command catalog, 상위 mapping과 report를 갱신합니다. 6. 생성 command catalog, 상위 mapping과 report를 갱신합니다.
+106
View File
@@ -0,0 +1,106 @@
# 코드가 처음인 사람을 위한 구조 안내
함수, 변수, 조건문은 알지만 웹 서버나 큰 프로젝트는 처음인 독자를 위한 글입니다.
이 문서를 읽고 나면 화면을 바꿀 파일, 게임 규칙을 바꿀 파일, 결과를 저장할 파일을
구분할 수 있습니다. 설치 전에 읽어도 됩니다.
## 먼저 게임을 한 문장으로 이해하기
플레이어는 장수 한 명의 행동을 미리 예약합니다. 서버는 장수의 차례가 되면
행동을 실행하고, 자원·도시·전투 결과를 저장합니다. 여러 플레이어와 컴퓨터 장수가
같은 세계에 참여하므로, 브라우저를 닫아도 세계는 계속 진행될 수 있습니다.
게임 자체가 낯설다면 [플레이어 첫 안내](../user/index.md)를 먼저 읽으세요.
## 화면과 서버는 다른 프로그램입니다
**프론트엔드**는 브라우저에서 실행되어 화면을 그리고 버튼 입력을 받는 프로그램입니다.
**백엔드**는 서버에서 실행되어 로그인과 권한을 확인하고 데이터를 읽거나 바꾸는
프로그램입니다. 버튼을 눌렀다고 브라우저가 직접 병력을 늘리는 것은 아닙니다.
브라우저는 서버에 요청하고, 서버가 허용한 결과를 받아 표시합니다.
**API**는 두 프로그램이 요청과 응답을 주고받는 약속입니다. 이 프로젝트는
TypeScript 타입으로 호출 형태를 연결하는 **tRPC**를 사용합니다. 타입이 맞더라도
사용자 권한과 실제 자원은 서버가 다시 검사해야 합니다.
예를 들어 “모병을 예약”하는 과정은 다음과 같습니다.
```text
브라우저: 병종과 수량을 고른다
↓ 저장 요청
게임 API: 로그인과 입력을 검사하고 예약 변경을 처리한다
↓ 예약 목록에 반영
턴 데몬: 내 차례가 되면 모병 조건을 다시 검사한다
↓ 실행 결과
데이터베이스: 병력, 자원, 로그와 남은 예약을 저장한다
↓ 알림을 받고 다시 조회
브라우저: 실제 결과를 보여 준다
```
예약 저장과 모병 실행은 서로 다른 일입니다. 예약 이후 돈을 쓰거나 도시를
잃었다면 실행에 실패할 수 있습니다. API를 읽을 때도 “요청을 받았다”와
“게임 행동이 끝났다”를 구분해야 합니다.
## 다섯 곳을 먼저 익히세요
| 위치 | 역할 | 찾아볼 때 |
| ------------------- | ------------------------------------- | -------------------------------------------- |
| `app/game-frontend` | Vue 화면과 사용자 입력 | 예턴 입력창이나 도시 화면을 이해하고 싶을 때 |
| `app/game-api` | 로그인·권한 검사, 조회·변경 요청 접수 | 누가 어떤 정보를 볼 수 있는지 궁금할 때 |
| `app/game-engine` | 시간에 맞춘 실행, 세계 상태와 저장 | 예약한 행동이 언제 실행되는지 궁금할 때 |
| `packages/logic` | 모병·내정·전투 등 게임 규칙 | 비용이나 성공 조건을 찾을 때 |
| `packages/infra` | 데이터베이스 구조와 연결 | 무엇을 영구히 저장하는지 찾을 때 |
`app`은 실행하는 프로그램, `packages`는 여러 프로그램이 함께 쓰는 코드 묶음입니다.
한 저장소에 여러 프로그램과 묶음이 있는 구성을 **모노레포**라고 합니다.
`pnpm`은 이 묶음들의 설치와 명령 실행을 관리합니다.
`resources`에는 지도, 병종, 시나리오 설정이 있습니다. 규칙 코드가 같아도 선택한
설정이 다르면 다른 게임이 됩니다. `tools`에는 검사·변환 같은 개발 도구가,
`docs`에는 지금 읽고 있는 문서가 있습니다.
## 로비와 게임을 나누는 이유
**Gateway**는 회원 계정과 로비를 담당합니다. 하나의 계정으로 여러 게임 서버에
들어갈 수 있지만, 각 게임의 장수·국가·진행 시점은 다릅니다. 운영 대상 게임을
코드에서는 **프로필(profile)**이라고 부릅니다. 프로필은 개인 프로필 사진과 다른
뜻입니다. **시나리오**는 그 게임에 적용할 시작 조건·지도·규칙 묶음입니다.
Gateway의 화면과 API는 `app/gateway-frontend`, `app/gateway-api`에 있습니다.
게임 업데이트와 실행 관리도 필요하므로 운영용 프로그램이 추가로 있습니다.
처음부터 이들을 모두 읽을 필요는 없습니다. [전체 구조](../architecture/overview.md)를
읽을 때 게임 진행 경로와 운영 경로를 나눠 보세요.
## 데이터는 어디에 남나요?
**메모리**는 실행 중인 프로그램이 빠르게 사용하는 작업 공간입니다. 프로그램을
다시 시작하면 사라질 수 있습니다. **PostgreSQL**은 세계 상태와 기록을 영구히
보관하는 데이터베이스입니다. **Prisma**는 코드에서 데이터베이스를 다루기 위한
도구이며, `packages/infra/prisma/game.prisma`는 게임 데이터 구조를 정의합니다.
턴 데몬은 세계를 메모리에 읽어 와 계산합니다. 계산이 끝나면 바뀐 값과 로그를
**트랜잭션(transaction)**으로 묶어 저장합니다. 모병으로 돈만 줄고 병력은 늘지
않는 식의 반쪽 저장을 막기 위한 경계입니다. 저장에 실패하면 메모리 상태도
되돌려야 다음 행동이 잘못된 세계에서 실행되지 않습니다.
**Redis**는 세션이나 작업 전달·알림 등에 쓰는 별도의 저장·통신 도구입니다.
게임 진행이 확정됐는지는 PostgreSQL 저장 결과를 기준으로 판단합니다.
**SSE**는 서버가 열린 브라우저로 변경 알림을 보내는 연결입니다. 알림이 늦었다고
곧바로 행동이 실패한 것은 아니므로 최신 데이터를 다시 조회합니다.
## 파일 하나에서 시작하는 읽기 연습
1. `packages/logic/src/actions/turn/general/che_모병.ts`를 엽니다.
입력값, 조건, 결과를 나눠 봅니다. 지금은 모든 계산을 외우지 않아도 됩니다.
2. `app/game-engine/src/turn/reservedTurnHandler.ts`를 읽으며 예약 행동이
실행되는 곳을 찾습니다. 규칙 파일과 실행 담당 파일이 다른 이유를 확인합니다.
3. `app/game-api/src/router/turns/`에서 예약을 바꾸는 요청을 찾습니다.
예약과 실행 사이에 시간이 흐른다는 점을 다시 확인합니다.
4. [파일 지도](./code-map.md)로 화면과 저장 담당 위치를 이어 봅니다.
파일명 뒤 `.ts`는 TypeScript 코드, `.vue`는 Vue 화면 구성요소, `.json`은 설정
데이터, `.prisma`는 데이터베이스 구조, `.test.ts`는 검증 코드입니다.
`import`는 다른 파일의 기능을 가져오는 선언입니다. 함수를 읽다가 모르는 이름을
만나면 선언 위치로 이동해 입력과 반환값부터 확인하세요.
다음은 [개발자 핸드북](./index.md)의 경험자 경로입니다. 실행 환경을 준비할 때는
저장소 `README.md`의 개발 환경과 [테스트 정책](../testing-policy.md)을 따릅니다.
@@ -4,9 +4,11 @@ Baseline: `main@b91dcbcaaac5acd4c7349cd3ed0996c547f58756`
Branch: `test/game-clock-reconciliation-20260903` Branch: `test/game-clock-reconciliation-20260903`
This plan is the status source for the long-running user test branch. A checked 이 문서는 위 기준선·branch에서 진행한 구현 계획의 기록입니다. 체크 표시는 당시
item means code and focused automated evidence exist on this branch; it does not 코드와 집중 검증이 있었다는 뜻이며, 현재 main·배포·운영 검증 상태를 나타내지 않습니다.
mean deployment or production validation. 현재 동작은 [게임 시계](../architecture/game-clock.md)와
[재정렬 계약](../architecture/game-clock-reconciliation.md), 장애 처리는
[복구 안내](./game-clock-recovery.md)를 읽으세요.
## Milestone 1 - authority and inventory ## Milestone 1 - authority and inventory
+47 -47
View File
@@ -1,61 +1,61 @@
# Game clock reconciliation recovery # 게임 시계 재정렬 실패 복구
This runbook is intentionally fail-closed. Do not force a profile to `RUNNING` 이 문서는 운영 경험이 있는 개발자를 위한 절차입니다. 개념부터 읽으려면
or delete an outbox row merely because its process is alive. [게임 시계](../architecture/game-clock.md), 계산·저장 계약은
[시계 재정렬](../architecture/game-clock-reconciliation.md)을 참고하세요.
프로세스가 살아 있다는 이유만으로 `RUNNING`을 강제하거나 outbox 행을 지우지 않습니다.
## Observe ## 먼저 관찰하기
Read only the target game schema. Record `world_state.clock_phase`, 대상 게임 schema에서 `world_state.clock_phase`, `clock_revision`,
`clock_revision`, `deadline_generation`, the latest `clock_suspension`, all its `deadline_generation`, 최신 `clock_suspension`, 참여자 checksum과 대응하는
participant checksums, and the matching `clock_projection_outbox`. Compare that `clock_projection_outbox`를 읽습니다. Redis의
target revision with `sammo:{profile}:clock:active-revision`. Never print DB or `sammo:{profile}:clock:active-revision`과 목표 revision을 비교합니다.
Redis credentials. DB·Redis 접속 비밀은 출력하지 않습니다.
## Status meaning **outbox**는 DB에서 확정했지만 다른 저장소에 전달해야 할 일을 보관한 원장입니다.
**checksum**은 재정렬 대상이 예상한 상태인지 비교하기 위한 요약값입니다.
DB와 Redis를 하나의 트랜잭션으로 묶었다고 가정하지 말고 두 단계의 상태를 봅니다.
- `SUSPENDED`: the cut is durable; no alignment DB transaction has committed. ## 상태를 해석하기
- `RECONCILING` with `PENDING`/`FAILED` outbox: DB schedules moved, Redis is not
authoritative yet, and gameplay must remain stopped.
- `RECONCILING` with `APPLIED` outbox: verify Redis active revision and all
participant checksums before finalizing.
- `RUNNING`: DB revision, deadline generation, and Redis active revision must
agree. A mismatch is an incident and workers must not dequeue.
## Retry | 상태 | 의미와 확인할 일 |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `SUSPENDED` | 중단 지점은 저장됐지만 일정 정렬 DB transaction은 아직 확정되지 않음 |
| `RECONCILING`, outbox `PENDING`·`FAILED` | DB 일정은 옮겼지만 Redis 전환이 끝나지 않음. 게임 진행을 열지 않음 |
| `RECONCILING`, outbox `APPLIED` | Redis 활성 revision과 모든 참여자 checksum을 확인하고 최종 확정 |
| `RUNNING` | DB revision·deadline generation·Redis 활성 revision이 일치해야 함. 불일치하면 worker가 작업을 꺼내서는 안 됨 |
Retry the same suspension ID and target revision through the clock-operation ## 같은 작업을 재시도하기
service. The service must re-read participant checksums and either return the
already-applied result or resume the pending outbox. Never create a replacement
revision to hide a failed target revision.
For `UNIFICATION_WAIT`, never rerun invader creation as a separate repair. 시계 작업 서비스를 통해 **같은 suspension ID와 목표 revision**으로 재시도합니다.
The input event, aligned schedules, optional rate, deterministic invader IDs, 서비스는 checksum을 다시 확인하고, 이미 적용됐다면 그 결과를 사용하거나 남은
reserved turns, and outbox committed together. A committed command with a outbox 처리를 이어갑니다. 실패를 숨기려고 새 revision을 만들지 않습니다.
`RECONCILING` world therefore needs only the same outbox retry. If the command
transaction rolled back, the original prompt and source revision remain and
the same response can be retried without changing IDs or RNG results.
When an outbox row is `FAILED`, the profile must remain `RECONCILING`. A retry `UNIFICATION_WAIT`에서는 이민족 생성을 별도 복구로 다시 실행하지 않습니다.
is safe in both crash locations: 입력 이벤트, 정렬된 일정, 선택적 간격 변경, 결정적인 이민족 ID, 예약 턴과 outbox가
함께 확정됩니다. 명령이 확정됐고 세계가 `RECONCILING`이라면 같은 outbox만 이어
처리합니다. transaction이 rollback됐다면 원래 질문·source revision이 남으므로
ID나 RNG 결과를 바꾸지 않고 같은 응답을 재시도할 수 있습니다.
- before Redis commit, the Lua operation reapplies from the source revision; 실패 위치에 따른 처리는 다음과 같습니다.
- after Redis commit but before DB finalization, the Lua operation returns the
already-active target result and DB finalization resumes without shifting any
tournament deadline twice.
`lastError` naming a legacy tournament deadline means the tournament lacks its - Redis 반영 전: Lua 작업이 원래 revision에서 적용합니다.
tick dual-write. Do not force the active revision; migrate or prove the - Redis 반영 후·DB 최종 확정 전: Lua가 이미 활성화한 결과를 돌려주고 DB 확정을
tournament inactive, then retry the same outbox. 이어갑니다. 토너먼트 기한을 두 번 이동하지 않습니다.
## Rollback `lastError`가 구형 토너먼트 기한을 지목하면 tick 기록이 갖춰졌는지 확인합니다.
활성 revision을 강제하지 말고 migration이나 비활성 상태의 근거를 확인한 뒤 같은
outbox를 재시도합니다.
There is no blind inverse update. Before enabling exact reconciliation in an ## 되돌리기와 검증 범위
environment, keep the normal database backup required for schema migrations.
If participant verification shows an unexpected mutation, stop the profile,
retain the ledger/outbox evidence, and restore the whole game schema from that
backup. Redis projections are then rebuilt from the restored DB revision.
The conditional clock suite exercises `SUSPENDED`, `RECONCILING/PENDING`, 모든 값을 반대로 이동하는 일괄 역연산은 복구 절차가 아닙니다. 운영 변경 전
`RECONCILING/FAILED` before and after Redis commit, recovered `APPLIED`, and 정상 백업을 준비하고, 예상하지 못한 mutation이 확인되면 대상 profile을 멈춰
final `RUNNING`. The admin status endpoint exposes the phase, revision, 원장·outbox 증거를 보존합니다. 백업 복원이 필요하면 해당 시점 이후 데이터 손실
participant checksums, and outbox error needed to choose the matching step. 범위를 평가하고 승인된 운영 복구 절차로 game schema를 복원합니다. Redis 투영은
복원한 DB revision에서 다시 구성합니다.
조건부 시계 통합 suite는 `SUSPENDED`, Redis 적용 전·후의 실패, `APPLIED` 복구와
최종 `RUNNING`을 검사합니다. 실행에 필요한 DB·Redis가 없어 skip된 결과는 복구
검증 성공이 아닙니다. [테스트 정책](../testing-policy.md)과 해당 suite 설정을 확인하세요.
+13 -3
View File
@@ -2,9 +2,16 @@
## 읽기 순서 ## 읽기 순서
신규 관리자 플레이 감사의 확정 범위, 수집·조회 비용과 후속 구현 체크리스트는 프로그래밍 기초만 안다면 [코드가 처음인 사람을 위한 구조 안내](./first-steps.md)에서
[프로필별 플레이 감사 설계](../design/play-audit.md)에 있습니다. 해당 기능은 시작하세요. 화면·서버·저장을 익힌 뒤 아키텍처 개요와 파일 지도로 넘어갑니다.
아직 미구현이며 아래의 현재 구조 설명과 상태를 구분합니다.
개발 경험이 있다면 [경험자를 위한 시스템 읽기](./system-walkthrough.md) →
[아키텍처 개요](../architecture/overview.md) → [요청·턴·저장](./request-turn-persistence.md)
순서가 좋습니다. 이후 아래 표에서 맡은 기능을 고르세요. 런타임 문서는 배포·worker까지
포함하는 상세 참고서입니다.
관리자 감사는 [운영 안내](../play-audit-operations.md)에서 현재 기능을,
[설계](../design/play-audit.md)에서 요구사항과 배경을 확인합니다.
| 작업 | 문서 | 코드 시작점 | | 작업 | 문서 | 코드 시작점 |
| --------------------- | --------------------------------------------------------------- | ------------------------------ | | --------------------- | --------------------------------------------------------------- | ------------------------------ |
@@ -18,6 +25,9 @@
| action module | [행동 모듈 프로토콜](../architecture/action-module-protocol.md) | `actionModules/` | | action module | [행동 모듈 프로토콜](../architecture/action-module-protocol.md) | `actionModules/` |
| ref 비교 | [차등 검증](../architecture/turn-state-differential-testing.md) | `tools/integration-tests` | | ref 비교 | [차등 검증](../architecture/turn-state-differential-testing.md) | `tools/integration-tests` |
[구조 문서 찾아보기](./reference-map.md)에는 시계·재시도·실시간 갱신·측정·운영 자료를
질문별로 모았습니다. 과거 계획과 현재 계약의 구분도 여기서 확인할 수 있습니다.
## 경계 ## 경계
- `packages/logic`은 계산과 규칙을 소유합니다. - `packages/logic`은 계산과 규칙을 소유합니다.
+47
View File
@@ -0,0 +1,47 @@
# 구조 문서 찾아보기
[기초 안내](./first-steps.md)와 [경험자 안내](./system-walkthrough.md)가 학습 경로를,
아래 문서들은 세부 계약을 제공합니다. 목록·벤치마크·과거 계획을 처음부터 모두
읽기보다 현재 질문에 해당하는 문서를 고르세요.
## 현재 구조와 구현 계약
| 질문 | 문서 |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 무엇이 어디서 실행되나요? | [개요](../architecture/overview.md), [런타임](../architecture/runtime.md) |
| 어느 파일부터 읽나요? | [파일 지도](./code-map.md), [패키지 경계](../architecture/package-boundaries.md) |
| 한 행동이 어떻게 계산되나요? | [도메인과 조립](./domain-and-classes.md), [행동 모듈](../architecture/action-module-protocol.md) |
| 언제 저장되고 실패는 어떻게 처리하나요? | [요청·턴·저장](./request-turn-persistence.md), [API 재시도](../architecture/api-input-event-replay.md) |
| 시간 정지·재개는 어떻게 되나요? | [게임 시계](../architecture/game-clock.md), [시간 도메인 목록](../architecture/time-domains.md), [재정렬 계약](../architecture/game-clock-reconciliation.md) |
| 설정을 여러 파일에서 조합하나요? | [시나리오 합성](../architecture/scenario-composition.md) |
| 화면에 변경이 어떻게 전달되나요? | [실시간 변경 원장](../architecture/realtime-change-journal.md) |
| 전투 시뮬레이터가 어디서 계산하나요? | [브라우저 Worker](../architecture/battle-simulator-browser-worker.md) |
| Ref와 무엇을 비교하나요? | [차등 검증](../architecture/turn-state-differential-testing.md) |
| TypeScript 버전이 왜 둘인가요? | [도구 체인 정책](../architecture/typescript-version.md) |
## 변경 범위를 빠짐없이 확인하는 목록
[엔진 호출 procedure 목록](../architecture/game-api-daemon-procedure-inventory.md)과
[직접 변경·journal 목록](../architecture/game-api-direct-mutation-journal-inventory.md)은
읽기 교재보다 변경 누락을 찾는 검토 자료입니다. 표의 조사 날짜·기준선과 현재
router·검사 코드를 함께 확인하세요. 새 API가 늘어도 과거 조사 숫자가 자동으로
현재 개수가 되지는 않습니다.
시계 참여자 JSON·mutation evidence TSV는 기계가 사용하는 자료입니다. 형식과
검사 계약을 유지하며 해당 기능을 바꿀 때 갱신합니다.
## 측정·계획·운영 자료
| 자료 | 해석 범위 |
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| [NPC 메모리 측정](../architecture/npc-lifecycle-memory-profile.md) | 지정 fixture의 메모리·생성/사망 부하. 운영 DB 처리량과 다름 |
| [NPC 천통 시간 측정](../architecture/npc-unification-timing-benchmark.md) | 인메모리 계산 시간. 실제 배포 성능 보장이 아님 |
| [시계 구현 계획 기록](./game-clock-reconciliation-plan.md) | 당시 branch의 milestone. 현재 운영 상태의 기준이 아님 |
| [시계 복구](./game-clock-recovery.md) | 상태·원장 확인과 같은 작업 재시도 |
| [릴리스 운영](../release-operations.md) | 실제 배포 작업과 준비 확인 |
| [관리자 콘솔](../admin-console.md), [플레이 감사 운영](../play-audit-operations.md) | 관리자 권한과 현재 조사 기능 |
| [테스트 정책](../testing-policy.md) | 실행 준비·검증 종류·skip의 해석 |
| [프론트엔드 CSS 구조](../frontend-css-architecture.md) | 화면 스타일의 소유권과 배치 계약 |
기능 설계의 목표, 코드에 있는 구현, 테스트로 확인한 범위, 실제 배포 상태는 서로
다릅니다. 문서의 “완료”는 함께 적힌 날짜와 검증 범위로 해석하세요.
+25 -14
View File
@@ -1,5 +1,10 @@
# 요청·턴·저장 흐름 # 요청·턴·저장 흐름
이 문서는 데이터베이스와 트랜잭션을 아는 독자를 위한 실행 흐름입니다. 처음이라면
[기초 구조 안내](./first-steps.md)를 먼저 읽으세요. **mutation**은 상태 변경,
**flush**는 메모리에서 바뀐 내용을 저장소에 반영하는 작업입니다.
조회·즉시 변경·예약 수정·시간에 따른 턴 실행을 구분해서 읽습니다.
## 조회 ## 조회
```text ```text
@@ -13,31 +18,35 @@ endpoint DTO에서 공개 field를 선택합니다.
## API transaction mutation ## API transaction mutation
```text ```text
request request → requestId·입력·actor·권한 검증
-> requestId와 input 검증 → PostgreSQL transaction
-> session actor·권한 검사 → API InputEvent 생성 또는 기존 row 잠금
-> InputEvent(target=API, PROCESSING) → identity 확인 → PROCESSING(attempts + 1)
-> Prisma transaction → savepoint 이후 업무 변경
-> domain row mutation → SUCCEEDED + 실제 result → commit
-> InputEvent(SUCCEEDED) → notification
-> commit
-> notification
``` ```
`app/game-api/src/inputEventBoundary.ts`의 `executeInputEvent()`가 이 경계를 `app/game-api/src/inputEventBoundary.ts`의 `executeInputEvent()`가 이 경계를
제공합니다. 중복 request ID는 완료·처리 중 event를 다시 실행하지 않으며, 제공합니다. identity는 event type, actor, payload digest를 포함합니다. 같은 요청의
실패 event는 claim 조건을 만족할 때 attempts를 증가시킵니다. 완료 결과는 재사용하고, 다른 내용으로 같은 ID를 사용하면 충돌로 거부합니다.
`PENDING`·`FAILED`는 identity가 맞으면 재시도할 수 있고, `PROCESSING`은 임의로
다시 선점하지 않습니다.
업무 오류는 savepoint까지 되돌린 뒤 실패 상태를 저장합니다. DB transaction
자체가 실패하면 그 안의 변경은 rollback됩니다. 상세 상태 표와 HTTP 응답 계약은
[API 입력 재시도](../architecture/api-input-event-replay.md)를 따릅니다.
## Daemon mutation ## Daemon mutation
```text ```text
request request
-> actor·input 검증 -> actor·input 검증
-> InputEvent(target=DAEMON) -> InputEvent(target=ENGINE)
-> daemon transport -> daemon transport
-> lease owner claim -> lease owner claim
-> in-memory world mutation -> in-memory world mutation
-> EngineStateManager transaction flush -> EngineStateManager의 메모리 복원 경계 안에서 DB transaction flush
-> PostgreSQL event 결과 commit과 in-memory world checkpoint 확정 -> PostgreSQL event 결과 commit과 in-memory world checkpoint 확정
-> SSE/realtime -> SSE/realtime
``` ```
@@ -60,7 +69,9 @@ request
7. PostgreSQL transaction에서 dirty state, turn queue, log와 event를 flush하고, 7. PostgreSQL transaction에서 dirty state, turn queue, log와 event를 flush하고,
같은 `EngineStateManager` 경계에서 world checkpoint를 확정합니다. 같은 `EngineStateManager` 경계에서 world checkpoint를 확정합니다.
Transaction 실패 시 `EngineStateManager`가 in-memory snapshot을 복원합니다. `databaseHooks.ts`가 PostgreSQL transaction을 담당하고, 이를 감싼
`EngineStateManager`가 실패 시 메모리 snapshot을 복원합니다. 이 관리자는 DB를
직접 알지 못합니다. DB rollback과 메모리 복원을 함께 유지해야 합니다.
Lease를 잃은 process는 fencing 검사에서 commit하지 못합니다. Lease를 잃은 process는 fencing 검사에서 commit하지 못합니다.
`InMemoryTurnStateStore`는 checkpoint를 별도로 복제하지 않고 rollback 대상인 `InMemoryTurnStateStore`는 checkpoint를 별도로 복제하지 않고 rollback 대상인
`InMemoryTurnWorld`에서 읽습니다. 따라서 flush 실패 뒤 다음 run도 복원된 `InMemoryTurnWorld`에서 읽습니다. 따라서 flush 실패 뒤 다음 run도 복원된
+88
View File
@@ -0,0 +1,88 @@
# 경험자를 위한 시스템 읽기
웹 개발이나 컴퓨터공학 배경이 있고, 이 저장소의 책임과 상태 전이를 빠르게
이해하려는 독자를 위한 안내입니다. 웹 용어가 낯설다면 [기초 안내](./first-steps.md)를
먼저 읽으세요.
## 세 가지 흐름으로 나누기
| 흐름 | 시작과 끝 | 핵심 질문 |
| ----------- | -------------------------------------------------------- | --------------------------------------------------------- |
| 사용자 요청 | 브라우저 → API → 저장 또는 엔진 입력 → 응답 | actor, 입력 검증, 중복 요청, 공개 범위는 누가 결정하는가? |
| 게임 진행 | 스케줄 → 장수 턴·월간 처리 → 저장 → 알림 | 실행 순서, 난수 소비, 실패 후 복원이 보존되는가? |
| 운영 | 관리자 요청 → 작업 원장 → 빌드·프로세스 전환 → 준비 확인 | 게임 상태와 배포 상태를 혼동하지 않는가? |
HTTP 요청 하나가 게임의 한 턴인 것은 아닙니다. 조회, 예약 수정, 즉시 동작,
시간에 따라 실행하는 턴은 서로 다른 수명주기를 가집니다. [전체 구조](../architecture/overview.md)에서
구성요소를 본 뒤 [요청·턴·저장](./request-turn-persistence.md)에서 경계를 읽으세요.
## 조회: 인증한 사람과 보여 줄 대상을 분리합니다
`app/game-api/src/server.ts`, `trpc.ts`, `router.ts`에서 transport와 인증 context,
procedure 조립을 따라갑니다. Gateway 계정과 게임 장수는 같은 식별자가 아닙니다.
클라이언트가 보낸 장수 번호만으로 소유권을 결정하지 않습니다.
구체적인 예는 `app/game-api/src/router/world/index.ts`의 `getCurrentCity`입니다.
자신의 위치, 같은 국가 장수의 위치, 소유 도시, 첩보와 인접 여부를 조합해
정보 공개 수준을 정합니다. 응답에 없는 비밀 값을 프론트엔드에서 가리는 방식이
아닙니다. 데이터 전달 객체(DTO)는 저장 행 전체와 다를 수 있습니다.
## 변경: 입력 원장과 결과의 원자성을 읽습니다
`InputEvent`는 받아들인 변경 요청과 처리 상태를 기록하는 PostgreSQL 모델입니다.
실제 `InputEventTarget` 값은 `API`와 `ENGINE`입니다. 엔진을 설명할 때 쓰는
“daemon”은 프로세스 역할이며 DB enum 값이 아닙니다.
API 쪽 경계는 `app/game-api/src/inputEventBoundary.ts`입니다. request ID만
같다고 모든 요청을 동일시하지 않습니다. actor, event type, payload identity와
상태를 함께 읽고 재시도·충돌·완료 결과 재사용을 구분합니다. 엔진 쪽 입력은
`app/game-api/src/daemon/`에서 따라갑니다.
예약 변경이 저장된 뒤 실제 턴 실행 조건은 다시 달라질 수 있습니다. request
acceptance 검증과 command execution 검증을 하나로 합치지 않습니다. API에서
완료된 변경과 엔진이 메모리에 보유한 세계 사이의 동기화도 확인해야 합니다.
## 엔진: 단일 소유자라도 장애 경계는 필요합니다
`createTurnDaemonRuntime()`의 조립부터 읽으면 loader, registry, AI, 월간 handler,
저장 hook이 연결됩니다. 계산 중 상태는 `InMemoryTurnWorld`, 저장과 실패 복원은
`EngineStateManager`, 실행 순서는 `TurnDaemonLifecycle`에서 추적합니다.
**Lease**는 일정 기간 엔진 소유권을 인정하는 임대입니다. **Fencing token**은
소유권 세대를 구분하여, 멈췄다가 돌아온 과거 프로세스가 새 소유자의 상태를
덮어쓰지 못하게 합니다. “프로세스가 한 개일 것”이라는 운영 가정만으로 대체할 수
없습니다. DB 저장 실패에는 메모리 rollback도 필요합니다.
난수도 입력 상태의 일부입니다. seed가 같아도 분기·정렬·호출 횟수가 바뀌면
이후 결과가 달라집니다. 도메인 계산은 [행동 모듈](../architecture/action-module-protocol.md),
비교 범위는 [차등 검증](../architecture/turn-state-differential-testing.md)을 읽으세요.
## 시간을 하나의 Date로 생각하지 않습니다
게임 진행 시각과 인증 만료·lease 같은 현실 시각은 목적이 다릅니다. 일시정지나
턴 간격 변경이 있다고 로그인 만료와 소유권 heartbeat를 같은 방식으로 옮길 수는
없습니다. [시간 도메인](../architecture/time-domains.md), [게임 시계](../architecture/game-clock.md),
[복구 절차](./game-clock-recovery.md) 순으로 읽으면 계산과 운영 경계가 이어집니다.
## 모듈과 프로세스도 다릅니다
`packages/logic`은 게임 계산을 소유하며 DB·파일·네트워크 I/O를 직접 하지 않습니다.
필요한 외부 동작을 interface인 port로 표현하고, app/infra 쪽 adapter를 주입합니다.
이 구분은 단위 검증과 런타임 저장 경계를 분리하기 위한 것입니다.
`game-api`가 `game-engine`의 일부 loader를 import한다고 API 안에서 턴 데몬을
실행한다는 뜻은 아닙니다. 공개 subpath와 프로세스 entrypoint를 구분하세요.
프론트엔드의 backend router 타입 참조도 서버 실행 코드를 브라우저에 넣는 것과
다릅니다. [패키지 경계](../architecture/package-boundaries.md)는 이 규칙의 기준입니다.
## 다음 조사 위치를 선택하기
- 화면 상태·재접속 문제: frontend store → 조회 응답 → [실시간 변경 원장](../architecture/realtime-change-journal.md)
- 새 게임 규칙: [도메인 조립](./domain-and-classes.md) → 명령·constraint → 엔진 결과 적용·flush
- 설정에 따른 차이: [시나리오 합성](../architecture/scenario-composition.md) → 선택한 resource → 실제 loader
- 계정·배포 문제: [런타임](../architecture/runtime.md) → [릴리스 운영](../release-operations.md)
- 관리자 조사 기록: [플레이 감사 운영](../play-audit-operations.md) → 수집 정책·snapshot·조회
코드 검증은 [테스트 정책](../testing-policy.md)을 따릅니다. 순수 계산 테스트,
실제 DB 저장 검증, 브라우저 검증은 서로 다른 질문에 답합니다. Ref는 계승 규칙의
비교 기준이며, 별도로 결정된 Core 기능까지 예전 구현으로 되돌리는 기준은 아닙니다.
+12 -10
View File
@@ -3,8 +3,8 @@ layout: home
hero: hero:
name: core2026 핸드북 name: core2026 핸드북
text: 코드와 게임 동작을 연결합니다 text: 처음 배우는 게임, 함께 이해하는 코드
tagline: 런타임, 저장 경계, 호환 검증과 플레이 방법을 현재 소스 구조에 맞춰 설명합니다. tagline: 게임이 처음인 플레이어부터 프로그래밍 입문자와 경험자까지, 필요한 순서로 읽는 안내서입니다.
actions: actions:
- theme: brand - theme: brand
text: 개발자 핸드북 text: 개발자 핸드북
@@ -30,17 +30,19 @@ features:
## 문서 안내 ## 문서 안내
개발자는 [개발자 핸드북](./developer/index.md)과 게임이 처음이라면 [삼국지 모의전투 첫 안내](./user/index.md)부터 읽으세요.
[아키텍처 개요](./architecture/overview.md)에서 시작해 주세요. 플레이어는 프로그래밍 기초만 안다면 [기초 구조 안내](./developer/first-steps.md), 개발 경험이
[시간과 턴](./user/time-and-turns.md)과 있다면 [경험자를 위한 시스템 읽기](./developer/system-walkthrough.md)에서 시작합니다.
[커맨드 목록](./user/command-catalog.generated.md)을 확인해 주세요. Profile과 [용어 사전](./user/glossary.md)은 국가 메시지와 게시판의 줄임말을 풀어 줍니다.
Profile과
Gateway 배포는 [릴리스 운영 매뉴얼](./release-operations.md)을 따라 주세요. Gateway 배포는 [릴리스 운영 매뉴얼](./release-operations.md)을 따라 주세요.
[Gateway와 게임 공통 메뉴 설정](./runtime-navigation.md)은 코드 재빌드 없이 [Gateway와 게임 공통 메뉴 설정](./runtime-navigation.md)은 코드 재빌드 없이
상단 링크와 dropdown을 바꾸는 JSON 형식과 복구 경계를 설명합니다. 상단 링크와 dropdown을 바꾸는 JSON 형식과 복구 경계를 설명합니다.
관리자 화면의 메뉴와 권한·운영 경계는 관리자 화면의 메뉴와 권한·운영 경계는
[관리자 콘솔](./admin-console.md)에서 확인할 수 있습니다. [관리자 콘솔](./admin-console.md)에서 확인할 수 있습니다.
[프로필별 플레이 감사 설계](./design/play-audit.md)는 현재 미구현인 국가·장수·도시· [플레이 감사 운영](./play-audit-operations.md)은 현재 제공하는 관리자 조사 기능과
외교·NPC 감사와 질문별 조사 도구의 구현 기준 및 DB 비용 검토를 정의합니다. 수집 범위를 설명합니다. [설계](./design/play-audit.md)는 요구사항과 비용·배경의 참고 문서입니다.
게임 진행 시각과 운영 벽시계의 경계는 게임 진행 시각과 운영 벽시계의 경계는
[게임 시계](./architecture/game-clock.md)에 설명합니다. [게임 시계](./architecture/game-clock.md)에 설명합니다.
[패키지와 파일 경계](./architecture/package-boundaries.md)는 source import와 [패키지와 파일 경계](./architecture/package-boundaries.md)는 source import와
@@ -52,8 +54,8 @@ Gateway 배포는 [릴리스 운영 매뉴얼](./release-operations.md)을 따
- `architecture/`: 현재 runtime, action module, scenario와 차등 검증 계약 - `architecture/`: 현재 runtime, action module, scenario와 차등 검증 계약
- `developer/`: 파일 위치, 도메인 조립, 요청·저장 흐름 - `developer/`: 파일 위치, 도메인 조립, 요청·저장 흐름
- `user/`: 화면, 시간, 국가 기능과 생성된 command catalog - `user/`: 게임 입문, 장수·내정·전쟁·외교, 게시판 용어와 명령 참고서
- `design/`: 확정한 신규 기능의 구현 목표·비용·검증 계약과 미구현 상태 - `design/`: 기능의 구현 목표·비용·검증 계약과 설계 배경
- 루트 문서: 통합 테스트, Chromium 비교, Caddy, DB 이관과 운영 절차 - 루트 문서: 통합 테스트, Chromium 비교, Caddy, DB 이관과 운영 절차
작업 이력은 상위 작업공간의 `report/`에 보존합니다. ref PHP와 core2026의 작업 이력은 상위 작업공간의 `report/`에 보존합니다. ref PHP와 core2026의
+16 -13
View File
@@ -1,17 +1,20 @@
# 커맨드와 실행 시기 # 커맨드와 실행 시기
커맨드는 장수나 국가에 내리는 **행동 명령**입니다. 처음에는 전체 목록을 외우지
말고 지금 신분과 목적에 맞는 절을 읽으세요. 전쟁 약어는 [모출·모훈사출출 안내](./war-and-orders.md)에 있습니다.
## 화면의 상태 표시 ## 화면의 상태 표시
서버는 내 장수, 도시, 국가와 현재 시점으로 각 커맨드를 사전 평가합니다. 서버는 내 장수, 도시, 국가와 현재 시점으로 각 커맨드를 사전 평가합니다.
| 표시 | 의미 | | 표시 | 의미 |
| -------------- | ----------------------------------------------------------------------------- | | -------------- | --------------------------------------------------------------------------------- |
| 사용 가능 | 현재 알려진 조건을 만족합니다. 실행 시 다시 검사합니다. | | 사용 가능 | 현재 알려진 조건을 만족합니다. 실행 시 다시 검사합니다. |
| 대상 선택 필요 | 도시·장수·국가·수량 같은 입력을 고르면 판정할 수 있습니다. | | 대상 선택 필요 | 도시·장수·국가·수량 같은 입력을 고르면 판정할 수 있습니다. |
| 사용 불가 | 현재 확정된 조건을 만족하지 않으며 이유가 함께 표시됩니다. | | 사용 불가 | 현재 확정된 조건을 만족하지 않으며 이유가 함께 표시됩니다. |
| 정보 부족 | 사전 화면에 없는 실행 context가 필요합니다. 실제 실행 성공을 뜻하지 않습니다. | | 정보 부족 | 사전 화면에 없는 실행 시점의 정보가 필요합니다. 실제 실행 성공을 뜻하지 않습니다. |
전체 key와 화면 이름은 [자동 생성 커맨드 목록](./command-catalog.generated.md)에 있습니다. 전체 명령 이름과 내부 식별자는 [자동 생성 커맨드 목록](./command-catalog.generated.md)에 있습니다.
## 시기별로 보는 장수 커맨드 ## 시기별로 보는 장수 커맨드
@@ -60,7 +63,7 @@
### 외교 상태에 따른 명령 ### 외교 상태에 따른 명령
선전포고는 기본 구현에서 scenario 시작 연도보다 적어도 1년 지난 뒤에 허용되며, 대상 국가와의 상태도 선전포고는 기본 구현에서 시나리오 시작 연도보다 적어도 1년 지난 뒤에 허용되며, 대상 국가와의 상태도
맞아야 합니다. 종전·불가침·불가침 파기 제안은 현재 외교 관계와 최소 기간을 검사합니다. 상대가 제안을 맞아야 합니다. 종전·불가침·불가침 파기 제안은 현재 외교 관계와 최소 기간을 검사합니다. 상대가 제안을
수락하는 즉시 행동은 일반 예약 국가 턴과 별도의 승인 흐름으로 처리됩니다. 수락하는 즉시 행동은 일반 예약 국가 턴과 별도의 승인 흐름으로 처리됩니다.
@@ -72,17 +75,17 @@
### 특수 병종 연구 ### 특수 병종 연구
`event_*연구` 명령은 scenario가 해당 명령을 profile에 넣고 필요한 연구 flag가 아직 없을 때 사용할 수 특수 병종 연구 명령은 시나리오가 해당 명령을 서버 설정에 넣고 해당 연구가 아직 완료되지 않았을 때 사용할 수
있습니다. 군주와 국고 조건을 검사하며 11턴 또는 23턴을 연속으로 쌓은 뒤 비용을 지불하고 병종 사용 있습니다. 군주와 국고 등의 조건을 만족한 상태에서 정해진 횟수만큼 연속으로 실행해야 합니다.
권한을 얻습니다. 소스의 선행 턴 값 11·23은 완료 턴을 포함하지 않으므로 화면에서 필요한 전체 실행은 기본 연구는 완료 턴까지 포함해 12턴 또는 24턴이 필요합니다. 중간에 다른 명령을 넣기 전에
각각 12턴·24턴입니다. 진행 조건과 남은 횟수를 확인하세요.
## 보호 기간과 연도 ## 보호 기간과 연도
scenario의 `startYear`와 `openingPartYear`가 초반 제한의 기준입니다. 다음은 대표적인 동작이며 서버별 시나리오의 시작 연도와 초반 보호 기간이 제한의 기준입니다. 다음은 대표적인 동작이며 서버별
상수에 따라 실제 연도가 달라집니다. 설정에 따라 실제 연도가 달라집니다.
- 출병은 예약 사전 판단과 실제 실행 모두 보호 기간을 고려합니다. - 출병의 예약 가능 시점과 실제 출병 가능 시점은 다를 수 있습니다. 미리 예약했더라도 실행 차례가 보호 기간 안이면 실패할 수 있습니다.
- 방랑, 의병 모집, 몰수 같은 일부 국가 변화도 초반에는 제한됩니다. - 방랑, 의병 모집, 몰수 같은 일부 국가 변화도 초반에는 제한됩니다.
- 기술 연구의 허용 기술 단계와 임관 인원 제한은 경과 연도에 따라 넓어질 수 있습니다. - 기술 연구의 허용 기술 단계와 임관 인원 제한은 경과 연도에 따라 넓어질 수 있습니다.
- 선전포고는 시작 직후가 아니라 상대 연도 1 이상에서 가능합니다. - 선전포고는 시작 직후가 아니라 상대 연도 1 이상에서 가능합니다.
@@ -94,5 +97,5 @@ scenario의 `startYear`와 `openingPartYear`가 초반 제한의 기준입니다
1. 메인 로그에서 실패 이유를 확인합니다. 1. 메인 로그에서 실패 이유를 확인합니다.
2. 자원·병력, 소속 도시와 보급, 직책, 대상과 외교 상태를 다시 확인합니다. 2. 자원·병력, 소속 도시와 보급, 직책, 대상과 외교 상태를 다시 확인합니다.
3. 여러 턴 뒤 실행될 명령이면 앞선 예턴이 상태를 바꾸는지 살펴봅니다. 3. 여러 턴 뒤 실행될 명령이면 앞선 예턴이 상태를 바꾸는지 살펴봅니다.
4. command table을 새로 불러와 대상 목록과 가능 상태를 갱신합니다. 4. 명령 입력창을 다시 열어 대상 목록과 가능 상태를 갱신합니다.
5. 동일 요청을 반복 전송하지 말고 화면이 최신 revision을 받은 뒤 다시 저장해 주세요. 5. 동일 요청을 반복 전송하지 말고 화면이 최신 예약 목록을 불러온 뒤 다시 저장해 주세요.
+75
View File
@@ -0,0 +1,75 @@
# 국가 재정과 외교
일반 장수도 세금과 외교의 뜻을 알면 국가 지시를 이해하기 쉬워집니다. 설정을
직접 바꾸는 일은 군주·담당 직책의 권한이 필요합니다.
## 세율과 지급률은 다릅니다
**세율**은 국가가 도시에서 거둘 수입과 주민·내정 변화에 영향을 줍니다.
**지급률**은 장수에게 나눠 주는 봉급 쪽 설정입니다. 세율 30%를 “군주가
내 돈의 30%를 가져간다”거나, 지급률 200%를 “국가 수입이 두 배”라는 뜻으로
읽으면 안 됩니다. 도시의 인구·농업·상업·민심과 국가 설정을 함께 봐야 합니다.
## 왜 세율 5%와 30%를 이야기하나요?
세율을 낮추면 당장 걷는 수입은 적지만 도시를 회복·성장시키기 좋고, 높이면
더 걷는 대신 도시 상태에 부담을 줍니다. 다음은 현재 기본 반기 처리의 비교입니다.
서버별 특수 효과, 도시 최대치, 반올림과 별도 인구 증가가 최종 수치를 바꿀 수 있습니다.
| 기본 처리에서 비교 | 5% | 30% |
| --------------------------------------- | ------- | ------------- |
| 같은 도시 상태에서 세율에 비례하는 수입 | 낮음 | 높음 |
| 내정치의 세율 보정 | +7.5% | −5% |
| 민심 변화 | +15 | −10 |
| 인구의 세율 보정 방향 | 증가 쪽 | 비례 증가분 0 |
내정은 이 보정 전에 기본 1% 감소도 거칩니다. 따라서 5%일 때 최종 내정이
무조건 7.5% 증가하는 것은 아닙니다. 30%에서 인구 비례 증가분이 0이어도 별도
기본 증가량이 있으므로 “인구가 전혀 늘지 않는다”는 뜻이 아닙니다.
나라가 가난한지, 전쟁 중인지, 도시가 충분히 성장했는지에 따라 판단이 달라집니다.
[10기 회고](https://sam.hided.net/xe/community/15146)에는 5% 세율과 높은 지급률로도
재정이 넉넉했던 사례가 있지만, 당시 나라 사정과 규칙에 따른 경험담입니다.
이를 모든 기수에 통하는 추천값으로 보지 마세요.
## 바꾸는 시점도 중요합니다
현재 구현은 월이 바뀔 때 적용할 세율을 따로 확정해 수입·반기 처리에 사용합니다.
화면에서 변경한 값이 이미 처리 중인 정산에 소급 적용되지는 않습니다.
월 변경 직전 접속해서 무조건 원하는 결과를 얻을 수 있다고 가정하지 마세요.
세율 담당자는 현재 설정, 다음 정산, 적용 결과와 국가 재정을 함께 확인합니다.
일반 장수라면 임의 변경보다 “모병비가 부족하다”, “이 도시의 인구가 줄었다”처럼
현재 문제를 알리는 편이 좋습니다. 계산·적용 시점의 코드 근거는
[자료 안내](./sources.md)에 모았습니다.
## 불가침·선포·개전·종전
**불가침**은 정한 기간 서로 공격하지 않는 관계입니다. **선포**는 선전포고를
줄인 말이고, **개전**은 실제 전쟁이 시작되는 시점입니다. 선포가 처리됐다고
그 즉시 아무 도시로나 출병할 수 있다고 가정하지 마세요. 외교 화면의 관계·시각과
출병 조건을 확인합니다. **종전**은 전쟁을 끝내는 합의입니다.
제안을 보낸 것과 상대가 수락한 것은 다릅니다. 합의가 필요하다면 수락 결과까지
확인하세요. 국가가 멸망하거나 직책·관계가 바뀌면 이전 제안을 처리할 수 없을 수도
있습니다.
## 최후 2국 외교는 자동 승리 규칙이 아닙니다
**최후 2국**은 마지막에 두 나라만 남은 상황을 뜻하기도 하고, 두 나라가 마지막까지
서로를 남겨 두자는 장기 외교 약속을 뜻하기도 합니다. 게시판이나 대화에서 어느
의미로 쓰였는지 먼저 구분해야 합니다.
“최후 2국 하자” 한 문장만으로는 원조, 다른 나라와의 전쟁 참여, 땅 교환과 길
열어 주기, 약속 종료 시점이 정해지지 않습니다.
[외교 옵션 건의](https://sam.hided.net/xe/devel/12712)는 이런 포괄적 표현의 모호함을
지적했습니다. 이것은 당시의 제안 글이며, 그 글의 모든 문장이 현재 서버의 공식
운영 규칙이라는 뜻은 아닙니다.
실제로 외교를 맡았다면 기간, 공격 관계, 원조, 땅·통로, 상황이 달라졌을 때의
처리를 구체적으로 협의하고 게임 안 외교 문서에 남깁니다. 게임의 불가침 설정과
사람 사이의 추가 약속은 구별해서 확인해야 합니다. 최후 2국이라는 약속만으로
두 나라가 공동 통일하거나 서버가 자동으로 약속을 집행하지는 않습니다.
처음 참가한 장수라면 국가가 합의한 공격 목표를 따르고, 다른 나라에 나라 전체를
대표하는 약속을 하기 전에 담당자에게 알려 주세요.
+65
View File
@@ -0,0 +1,65 @@
# 장수와 도시, 내정
[첫 안내](./index.md)를 읽었다면 이제 내 화면의 숫자를 살펴봅시다.
모든 수치를 외우기보다 “어떤 일을 하려는데 무엇이 부족한가”를 찾으면 됩니다.
## 장수의 능력과 자원
| 항목 | 처음에는 이렇게 이해하세요 |
| --------- | --------------------------------------------------- |
| 통솔 | 이끌 수 있는 병력 규모와 관련된 능력 |
| 무력 | 무장을 중심으로 한 전투와 관련된 능력 |
| 지력 | 지장을 중심으로 한 전투·내정과 관련된 능력 |
| 금 | 모병, 장비, 내정 등 행동에 쓰는 돈 |
| 쌀·군량 | 병력을 운용하고 전쟁을 이어 가는 식량 |
| 병력 | 현재 이끌고 있는 병사의 수 |
| 훈련·사기 | 지금 병력이 얼마나 싸울 준비가 되었는지 나타내는 값 |
| 숙련도 | 병종별 전투 경험과 관련된 값 |
| 특기·장비 | 특정 행동이나 전투에 영향을 주는 추가 효과 |
능력치만 비슷하다고 전투력이 같지는 않습니다. 병종, 병력 수, 훈련·사기,
숙련도, 특기, 장비, 부상과 전투 상황이 함께 작용합니다. 모병으로 병사를 채우는
일과 훈련·사기로 전투 준비를 하는 일도 다릅니다.
**무장**은 무력을, **지장**은 지력을 중심으로 키우는 장수를 가리키는 말입니다.
국가가 원하는 역할과 접속 가능한 시간을 먼저 상의하면 능력 배분과 할 일을 정하기
쉽습니다. 전쟁에 자주 접속하지 못해도 내정과 예약 행동으로 기여할 수 있습니다.
## 도시는 나라의 생활 기반입니다
도시는 병사를 모을 주민, 수입을 만드는 농업·상업, 치안과 방어 시설을 가집니다.
이것들을 돌보는 일을 **내정**이라고 합니다. 지도에 우리 색의 도시가 많아도
주민과 내정이 무너지면 전쟁을 오래 이어 가기 어렵습니다.
| 행동 | 목적 | 먼저 볼 것 |
| ------------------- | ------------------------------- | ------------------------------------ |
| 농지 개간 | 농업을 올려 식량 생산 기반 마련 | 농업이 이미 최대인지 |
| 상업 투자 | 상업을 올려 금 수입 기반 마련 | 상업이 이미 최대인지 |
| 치안 강화 | 도시의 치안 개선 | 현재 치안과 국가 지시 |
| 정착 장려 | 주민 확보 | 현재 인구와 최대 인구 |
| 성벽 보수·수비 강화 | 도시 방어 준비 | 전선이 될 도시인지, 부족한 방어 수치 |
| 기술 연구 | 국가의 기술 발전 | 허용 기술 단계와 필요한 자원 |
현재 도시와 소속 국가, 보급, 비용 등의 조건을 만족해야 합니다. 이미 최대인
항목만 계속 예약하면 원하는 성과를 얻지 못할 수 있으니 다른 도시나 일을
국가에 물어보세요. [명령별 조건](./commands-and-timing.md)에서 확인할 수 있습니다.
## 내 금·쌀과 국고는 다릅니다
국가가 부유해도 내 장수의 돈이 부족할 수 있습니다. 국고에서 장수에게 자원을
나누는 **포상**, 장수의 자원을 국가로 모으는 **몰수**, 나라 사이의 **원조**는
대상과 권한이 다릅니다. 전쟁 중 모병이 막혔다면 “돈이 없어요”보다
“어느 도시에서 어떤 병종을 몇 명 모으려는데 금이 부족합니다”라고 알려 주세요.
봉급과 세금은 [국가 재정](./economy-and-diplomacy.md)에서 이어 설명합니다.
장비 구매·경매·유니크 획득은 서로 다른 경로입니다. **유니크**는 특별한 장비를
부르는 말이며, 모든 서버에서 원하는 장비를 즉시 살 수 있다는 뜻은 아닙니다.
## 처음 막혔을 때
내정이 실패하면 현재 도시가 우리 땅인지, 보급이 되는지, 비용이 있는지, 수치가
이미 최대인지부터 봅니다. 전쟁으로 도시 주인이 바뀌거나 발령으로 위치가 달라졌다면
이전 예턴이 지금 상태에 맞지 않을 수 있습니다. 행동 기록의 실패 이유를 읽고
가까운 예약부터 다시 정하세요.
다음은 [시간과 턴](./time-and-turns.md)입니다.
+116
View File
@@ -0,0 +1,116 @@
# 국가 메시지와 게시판 용어 사전
모르는 말이 나오면 여기서 찾고, 자세한 행동은 연결한 안내를 읽으세요.
**기본 용어**는 화면·명령의 뜻, **커뮤니티 표현**은 이용자들이 줄여 부르는 말입니다.
전략 표현 자체가 별도 게임 기능이나 운영 규칙을 뜻하지는 않습니다.
조사한 글과 현재 코드의 구분은 [자료 안내](./sources.md)에 있습니다.
## 참가와 역할
| 말 | 뜻 |
| ------------- | ----------------------------------------------------------------------- |
| 삼모 | 삼국지 모의전투의 줄임말 |
| 기수·깃수 | 시작부터 종료까지 한 차례 진행하는 게임 |
| 천통 | 천하통일. 나라가 통일을 이루는 것 |
| 군주 | 국가를 이끄는 장수 |
| 수뇌 | 국가 운영을 맡는 주요 직책의 장수들을 묶어 부르는 말 |
| 사령턴·국가턴 | 권한을 가진 장수가 넣는 국가 명령의 예약 턴 |
| 무장·지장 | 무력 또는 지력을 중심으로 키우는 장수 |
| 무지장 | 무력과 지력을 함께 중심으로 둔 장수라는 표현 |
| 유저장 | 사람이 맡는 장수 |
| NPC·엔장 | 컴퓨터가 행동을 정하는 장수 |
| 빙의 | 허용된 기존 NPC를 이용자가 맡는 참가 방식 |
| 임관 | 나라에 들어가 소속 장수가 되는 것 |
| 랜임 | 무작위 임관. 나라를 직접 고르지 않는 임관 |
| 지정임관 | 특정 나라를 골라 들어가는 임관을 가리키는 말 |
| 선약 | 함께 참가하기로 미리 한 약속. 서버의 가입 제한보다 우선하는 기능은 아님 |
| 재야 | 소속 국가가 없는 장수 상태 |
| 하야 | 관직·소속을 내려놓고 나라를 떠나는 명령 |
| 방랑 | 도시를 가진 정착 국가와 다른 국가 상태. 단순 장수 이동과 구분 |
| 열전 | 기수가 끝난 뒤 활동과 이야기를 회고한 글, 또는 장수의 이력 |
역할과 참가의 실제 사용례는 [97기 장수열전](https://sam.hided.net/xe/community/78162)과
[43기 국가열전](https://sam.hided.net/xe/community/25102)에서 확인했습니다.
## 소통과 접속
| 말 | 뜻 |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 예턴 | 예약 턴. 접속하지 않는 동안에도 순서대로 실행할 행동 목록 |
| 실접 | 실제로 접속해 상황을 보며 조작하는 것 |
| 국메·국가 메시지 | 같은 나라에 보내는 메시지 |
| 갠메·개인 메시지 | 특정 사람에게 보내는 메시지. 글에 따라 갠매로도 씀 |
| 전메·전체 메시지 | 전체 이용자에게 보이는 메시지 |
| 국톡 | 국가 구성원의 외부 단체 대화방을 가리키는 말 |
| 국방 | 문맥상 국가방침의 줄임말일 수 있음. “국가 방어”라는 보통 뜻과 구분 |
| 호출 | 상황에 맞춰 접속·행동을 부탁하는 말 |
| 삭턴 | 화면에서는 장수 삭제까지의 남은 턴과 관련된 값. 대화의 “삭턴 난다”는 명령 실패 등으로 행동 기회를 낭비한다는 뜻으로도 쓰임 |
| 벌점 | 활동 상태와 관련해 표시하는 값. 정확한 조건은 현재 화면 안내를 확인하고, 운영 제재와 같은 것으로 단정하지 않음 |
[뉴비 질문 모음](https://sam.hided.net/xe/community/51784)에는 삭턴·벌점 질문이,
[97기 회고](https://sam.hided.net/xe/community/78162)에는 실패를 피하려고 발령과 경로를
조정하는 맥락의 삭턴 표현이 나옵니다. 같은 단어라도 문맥을 확인하세요.
## 내정과 자원
| 말 | 뜻 |
| ----------- | --------------------------------------------------------------- |
| 내정 | 농업·상업·치안·인구·방어·기술 등 나라의 기반을 돌보는 행동 |
| 농상 | 농업과 상업을 함께 부르는 말 |
| 기연 | 기술 연구 |
| 금쌀 | 금과 쌀을 함께 부르는 말 |
| 국고·군량 | 국가의 돈·식량. 내 장수의 금·쌀과 구분 |
| 포상 | 국가 자원을 장수에게 지급하는 명령 |
| 몰수 | 장수 자원을 국가로 회수하는 명령 |
| 원조 | 나라 사이에 물자를 보내는 물자 원조 |
| 물조 | 물자조달. 장수가 자원을 마련하는 내정 명령으로 물자 원조와 다름 |
| 세율 | 수입과 주민·내정 변화에 영향을 주는 국가 설정 |
| 지급률 | 장수 봉급 지급과 관련된 국가 설정 |
| 유니크·유닉 | 특별한 장비를 부르는 말 |
| 유산 | 기수를 넘겨 활용하는 포인트와 그 기능. 현재 소지 금·쌀과 다름 |
| 템 | 아이템·장비 |
금·쌀이 어디에 쓰이는지는 [장수와 도시](./general-and-city.md), 세율 5%·30%의
차이는 [재정 안내](./economy-and-diplomacy.md)를 읽으세요. 기연·유니크·유산의
사용례는 [97기 회고](https://sam.hided.net/xe/community/78162)에 있습니다.
## 전쟁과 지도
| 말 | 뜻 |
| -------------- | ---------------------------------------------------------------------- |
| 모·징·훈·사·출 | 각각 모병·징병·훈련·사기 진작·출병 |
| 모출·모출출 | 모병 후 출병 한 번 또는 두 번을 예약하는 표현 |
| 모훈사출출 | 모병 → 훈련 → 사기 진작 → 출병 → 출병 |
| 징훈훈사사 | 징병 뒤 훈련 두 번과 사기 진작 두 번 |
| 풀훈사 | 훈련·사기를 충분히 채운 병력 상태 |
| 양파·모병양파 | 병력을 반복 보충하면서 수비를 이어 가는 방식 |
| 점사 | 특정 목표·시점에 공격을 집중하는 전술 |
| 턴 정렬 | 동료의 행동·공격 순서를 맞추는 것 |
| 병종 저격 | 상대에게 유리한 상성의 병종으로 싸우려는 것. 특기 “저격”과 구분 |
| 수비 켬끔 | 수비 참여를 켜고 끄며 전투 참여를 조절하는 플레이 |
| 땅따 | 땅따먹기. 특히 초반 공백지를 선점하는 확장 경쟁 |
| 공백지 | 어느 나라에도 속하지 않은 도시 |
| 접경·전선·후방 | 다른 나라와 닿는 곳·전쟁이 벌어지는 곳·그 뒤의 지역 |
| 깃 끊기 | 수도에서 이어지는 보급 연결을 끊는 전술 |
| 길막·길 열기 | 진출할 통로를 차지해 막거나, 지나갈 수 있도록 조정하는 것 |
| 수복 | 빼앗긴 도시를 되찾는 것 |
| 발령 | 국가 명령으로 장수·부대의 배치를 바꾸는 것 |
| 집합·집합장 | 부대원을 모으는 행동·이를 맡는 장수. 병사 한 명을 뜻하지 않음 |
| 쟁·연쟁 | 전쟁·연속되는 전쟁 |
| 다굴 | 여러 나라가 한 나라를 함께 공격하는 상황을 가리키는 표현 |
| 선포 | 선전포고 |
| 불가침 | 정한 기간 서로 공격하지 않는 관계 |
| 최후 2국 | 마지막 남은 두 나라 또는 그때까지 서로 남겨 두자는 외교 약속 |
| 의병 | 의병 모집으로 동원하는 전력. 일반 모병과 구분 |
| 필즉·백동 | 국가 전략 명령인 필사즉생·백성 동원의 줄임말 |
| 전금 | 전쟁 금지 방침을 줄여 부르는 말. 적용 대상·설정 범위를 확인 |
| 살상률·교환비 | 적에게 준 병력 손실과 내가 입은 손실을 비교하는 지표·표현. 승률과 다름 |
조합과 수비는 [전쟁 안내](./war-and-orders.md), 보급과 땅따는
[지도 안내](./map-and-supply.md), 최후 2국은 [외교 안내](./economy-and-diplomacy.md)에서
사례와 주의점을 설명합니다. [70기 전쟁 회고](https://sam.hided.net/xe/community/47834)는
양파·발령·다굴·전쟁 금지·필즉의 사용 맥락을,
[99기 국가열전](https://sam.hided.net/xe/community/79329)은 외교·의병·전금·백동의 맥락을
보여 줍니다. 사전의 설명은 원문을 그대로 옮긴 것이 아니라 처음 읽는 사람을 위한 풀이입니다.
물조의 역사적 사용례는 [물조 명성 관련 건의](https://sam.hided.net/xe/devel/2569)에 있습니다. 당시 보상 변경 제안을 현재 효과로 읽지 마세요.
+50 -29
View File
@@ -1,37 +1,58 @@
# 플레이어 가이드 # 삼국지 모의전투, 처음부터 시작하기
core2026에서는 지금 누르는 버튼이 즉시 모든 결과를 만드는 것이 아니라, 많은 행동을 **예턴**에 넣고 내 삼국지 모의전투는 **여러 사람이 장수 한 명씩을 맡아, 같은 나라를 키우고 다른 나라와
장수의 다음 턴에 실행합니다. 국가 커맨드도 같은 방식이지만 군주·직책과 국가 상태에 따라 이용 범위가 경쟁하는 웹 전쟁 게임**입니다. 삼국지 소설이나 프로그래밍을 몰라도 시작할 수 있습니다.
달라집니다. 혼자 나라의 모든 병사를 움직이는 게임과 달리, 동료들과 역할과 행동 시간을 맞추는
일이 중요합니다. 직접 싸우는 사람도, 돈과 식량을 마련하는 사람도 나라에 필요합니다.
## 처음 접속했을 때 ## 내가 움직이는 것은 장수 한 명입니다
1. Gateway에서 로그인하고 열려 있는 서버 profile을 선택합니다. **장수**는 게임 속 내 인물입니다. **국가**는 여러 장수가 함께 속하는 편이고,
2. 게임에 장수가 없다면 장수 생성·참가 화면에서 이름, 능력치 등 필요한 정보를 정합니다. **도시**는 지도 위의 거점입니다. 장수는 한 도시에 머물며 일하거나 병사를 모으고,
3. 메인 화면에서 현재 도시·국가·자원·다음 턴 시각을 확인합니다. 다른 도시로 이동하거나 공격합니다. 컴퓨터가 움직이는 장수는 **NPC**, 이용자들은
4. 예턴 목록의 가까운 순서부터 실행할 커맨드를 지정합니다. 줄여서 **엔장**이라고 부릅니다.
5. 국가에 소속됐다면 국가 정보·도시·장수·외교 화면에서 상황을 확인합니다.
서버마다 scenario, 시작 연도, 턴 간격, 가입 방식, map, 병종과 허용 커맨드가 다를 수 있습니다. 이 문서는 나라들은 도시를 늘리고 전쟁·외교를 거쳐 통일을 목표로 합니다. 한 차례의 게임을
기본 구현을 설명하며 현재 화면의 가능/불가 표시와 운영 공지를 우선해 주세요. **기수** 또는 **깃수**, 천하통일을 **천통**이라고 부릅니다. 한 기수가 끝나면 다음
기수에서는 새 상황에서 다시 시작합니다. 개인적으로는 전투·내정·성장·기록을 즐길
수도 있습니다. 처음부터 군주가 되어 나라를 운영할 필요는 없습니다.
## 어디서 무엇을 하나요? ## 버튼을 누른 순간 모두 실행되지는 않습니다
| 메뉴 | 용도 | 이 게임의 **턴**은 내 장수가 행동할 차례입니다. **예턴**은 앞으로 할 행동을
| -------------------------- | ---------------------------------------------- | 미리 적어 놓은 목록입니다. “농지 개간 → 농지 개간 → 이동”을 넣었다면 내 차례가
| 메인 | 장수·국가 예턴, 현재 상태, 최근 기록 | 올 때마다 앞에서부터 하나씩 실행합니다. 저장 직후 도시가 바뀌지 않아도 이상한
| 현재 도시·국가 정보 | 도시 내정치·보급·전선, 국가 자원·기술 | 것은 아닙니다. 실제 행동은 다음 턴에 일어납니다.
| 국가 장수·인사 | 소속 장수와 직책, 권한이 있으면 인사·방침 설정 |
| 외교 | 국가 관계와 제안·교전 상태 확인 |
| 부대 | 부대 생성·가입·관리 |
| 경매·토너먼트·베팅 | 해당 기간에 열린 참가·입찰·예측 기능 |
| 게시판·메시지 | 공개/국가 소통과 개인 메시지 |
| 연감·명장·왕조·과거 플레이 | 현재·과거 기록 열람 |
| 내 정보·설정 | 표시·알림 등 개인 설정 |
## 다음에 읽을 문서 로그아웃해도 예약한 행동은 진행됩니다. 다만 전쟁으로 길이 막히거나 자원이
부족해지면 실패할 수 있습니다. 접속할 때는 **현재 상태 → 최근 기록 → 다음 예턴**
순서로 확인하세요. [시간과 턴](./time-and-turns.md)에 예시가 있습니다.
- 예턴이 언제 실행되는지: [시간과 턴](./time-and-turns.md) ## 첫 접속에서 할 일
- 어떤 조건에서 커맨드를 쓸 수 있는지: [커맨드와 실행 시기](./commands-and-timing.md)
- 현재 소스에 등록된 전체 목록: [커맨드 전체 목록](./command-catalog.generated.md) 1. 로그인한 뒤 로비에서 참가할 게임 서버를 고릅니다. 서버마다 지도·속도·규칙이
- 국가 직책과 부가 기능: [국가 운영과 주요 기능](./nation-and-features.md) 다를 수 있으니 현재 공지와 시작 시각을 읽습니다.
2. 장수 생성·참가 화면의 안내를 따릅니다. 이름과 능력치를 정하는 방식, 기존
NPC를 맡는 **빙의** 가능 여부는 서버 설정에 따라 다릅니다.
3. 메인에서 내 **국가, 현재 도시, 금, 쌀, 다음 턴 시각**을 찾습니다.
4. 나라가 없다면 **임관**으로 나라에 들어갑니다. 무작위 임관만 허용하는 서버도
있습니다. 나라가 있다면 국가 공지와 메시지를 먼저 읽습니다.
5. “처음입니다. 어느 도시에서 어떤 일을 하면 될까요?”라고 국가 메시지로 물어보고,
가까운 예턴부터 입력합니다. 저장된 목록이 원하는 순서인지 확인합니다.
6. 다음 접속 때 행동 기록에서 성공·실패 이유를 읽고 남은 예턴을 고칩니다.
## 어디까지 읽으면 되나요?
| 지금 필요한 것 | 읽을 문서 |
| ------------------------------- | ------------------------------------------------- |
| 내 장수와 돈·쌀·도시가 무엇인지 | [장수와 도시, 내정](./general-and-city.md) |
| 행동을 예약하고 결과를 확인하기 | [시간과 턴](./time-and-turns.md) |
| 처음 전쟁에 참여하기 | [전쟁과 예턴 조합](./war-and-orders.md) |
| 지도에서 길과 정보 읽기 | [보급·정찰·땅따](./map-and-supply.md) |
| 세율과 외교를 이해하기 | [국가 재정과 외교](./economy-and-diplomacy.md) |
| 게시판과 국가 메시지의 줄임말 | [용어 사전](./glossary.md) |
| 메뉴와 부가 기능 찾기 | [국가 운영과 주요 기능](./nation-and-features.md) |
| 특정 명령의 조건 확인하기 | [커맨드와 실행 시기](./commands-and-timing.md) |
전략 글은 당시 규칙과 경험에 따른 조언입니다. 이 안내서는
[게시판 원문과 확인 근거](./sources.md)를 연결하고, 현재 규칙과 경험담을 구분합니다.
+69
View File
@@ -0,0 +1,69 @@
# 보급·정찰·땅따: 지도 읽기
지도에서는 도시 색뿐 아니라 **도시 사이의 연결선, 수도, 보급 상태**를 함께 봅니다.
가까워 보이는 두 도시라도 길이 연결되지 않았다면 바로 이동하거나 공격할 수
없을 수 있습니다.
## 수도에서 이어지는 길이 보급로입니다
보급은 나라의 도시들이 수도와 같은 나라의 도시를 거쳐 연결되어 있는지에 관한
상태입니다. 단순히 내 장수가 쌀을 많이 들고 있다는 뜻은 아닙니다.
```text
수도 A ─ 우리 도시 B ─ 우리 도시 C
```
다른 길이 없을 때 적이 B를 점령하면 C는 수도와 끊깁니다. C를 직접 공격하지
않아도 보급을 위협할 수 있습니다. 반대로 우회하는 우리 도시의 연결이 있다면
B 하나를 잃어도 이어질 수 있습니다.
## 깃 끊기·깃발이 떨어지기
이 맥락에서 **깃 끊기**는 보급 연결을 끊는 전술을 말합니다. 게임 한 판을 뜻하는
“깃수”와 구분하세요. [43기 국가열전](https://sam.hided.net/xe/community/25102)에는
연결 도시를 빼앗아 여러 지역의 보급을 끊고, 상대가 연결을 되찾는 공방이 나옵니다.
현재 기본 보급 처리는 수도에서 같은 나라 도시의 연결을 따라 판정합니다.
보급이 끊긴 도시는 내정·인구 등에서 손해를 받고, 그곳의 소속 장수도 병력과
훈련·사기가 줄어들 수 있습니다. 모병·내정 등 보급을 요구하는 행동도 확인해야
합니다. 도시 하나의 손실이 뒤쪽 여러 도시의 문제로 번지는 이유입니다.
도시 점령 순간과 보급 갱신 시점은 구분해야 합니다. 화면의 보급 표시와 월 변경
뒤의 기록을 확인하고, 고립된 곳에서 모병만 계속 예약하지 마세요. 국가 운영자는
연결 도시 수복이나 수도 위치 등 가능한 회복 수단을 살펴야 합니다.
## 장수 이동으로 도시 정보 밝히기
내 장수가 있는 도시는 현재 도시 화면에서 자세히 볼 수 있습니다. 따라서
허용된 **이동**으로 도시를 직접 살펴보는 것도 정보 수집 방법입니다. 이동은
도시를 점령하는 행동이 아니므로, 정보 확인과 공격을 혼동하지 않습니다.
현재 도시 조회는 자신의 위치, 소속 국가의 도시와 장수 위치, 첩보, 인접 여부
등에 따라 공개 범위를 나눕니다. 같은 국가의 관직을 가진 장수는 아군 장수가
있는 도시를 조회할 수 있는 조건도 있습니다. 그래서 장수 한 명의 위치가 국가의
정보 수집에 도움이 될 수 있습니다. 다만 도시가 보인다고 적 장수의 모든 비밀
정보나 예약 행동까지 공개되지는 않습니다.
이동은 거리·비용 등 실행 조건을 확인해야 하고, 자리를 비우면 원래 도시에서
하던 내정·수비·모병 계획이 달라집니다. 떠난 뒤에도 정보가 영구히 남는 탐험
게임으로 생각하지 마세요. **첩보**로 얻는 정보와 직접 주둔해서 보이는 정보도
구분합니다. 이 설명은 현재 도시 조회 코드로 확인한 기능이며, 같은 표현의
게시판 공략을 발견했다고 주장하는 것은 아닙니다. 근거는 [자료 안내](./sources.md)에 있습니다.
## 땅따와 183년
**땅따**는 땅따먹기의 줄임말입니다. 특히 게임 초반 출병 제한이 풀린 뒤 공백지
등을 선점해 나라의 영역과 접경을 만드는 시기를 가리킵니다. **공백지**는 어느
나라에도 속하지 않은 도시이며, 아무 준비 없이 점령할 수 있다는 뜻은 아닙니다.
게시판에서 자주 보이는 **183년 땅따**는 그 시나리오의 시작 연도와 보호 기간을
배경으로 합니다. [84기 회고](https://sam.hided.net/xe/community/72721)에는
183년 초 땅따가 끝난 사례가 있습니다. 모든 서버가 반드시 183년에 열리는 것은
아닙니다. 현재 서버의 공지와 출병 가능 시점을 확인하세요.
땅따 전에는 병력·훈련·사기·군량을 준비하고 동료와 목표 도시를 나눕니다.
가까운 성 하나만 보지 말고 그 뒤로 갈 길과 수도 연결, 다른 나라와 닿을 국경을
살펴보세요. 너무 넓게 뻗으면 지킬 곳이 늘어나고, 다른 나라의 확장과 부딪힐 수
있습니다. 이미 외교로 약속한 경로가 있는지도 국가 공지에서 확인합니다.
다음은 [국가 재정과 외교](./economy-and-diplomacy.md)입니다.
+12 -10
View File
@@ -6,8 +6,9 @@
전략·재정 설정, 비밀 장수 정보와 정찰 차단 같은 기능에 적용됩니다. 같은 국가 장수에게만 보이는 정보도 전략·재정 설정, 비밀 장수 정보와 정찰 차단 같은 기능에 적용됩니다. 같은 국가 장수에게만 보이는 정보도
있고, 공개 목록에는 일부 값이 가려질 수 있습니다. 있고, 공개 목록에는 일부 값이 가려질 수 있습니다.
내가 직접 URL이나 장수 번호를 입력해도 권한은 늘어나지 않습니다. 서버가 로그인 session에서 내 장수를 직책은 단순한 명예 표시가 아니라 실제 이용 권한과 연결됩니다. 처음에는 군주나
찾아 국가·직책·대상 관계를 판단합니다. 수뇌(국가 운영 담당 장수)에게 맡을 역할을 물어보세요. 권한이 없는 메뉴가 보이지
않거나 일부 정보가 가려지는 것은 정상일 수 있습니다.
## 국가 운영 화면 ## 국가 운영 화면
@@ -28,7 +29,7 @@
## 외교와 즉시 승인 ## 외교와 즉시 승인
선전포고 같은 일부 외교 행동은 국가 예턴으로 실행합니다. 종전·불가침·파기 제안은 제안 생성과 상대의 선전포고 같은 일부 외교 행동은 국가 예턴으로 실행합니다. 종전·불가침·파기 제안은 제안 생성과 상대의
수락이 나뉘며, 수락은 현재 관계와 권한을 다시 확인하는 즉시 action입니다. 제안 뒤 국가가 멸망하거나 수락이 나뉘며, 수락은 현재 관계와 권한을 다시 확인하는 즉시 처리입니다. 제안 뒤 국가가 멸망하거나
관계·직책이 바뀌면 수락할 수 없을 수 있습니다. 관계·직책이 바뀌면 수락할 수 없을 수 있습니다.
## 부대 ## 부대
@@ -38,14 +39,13 @@
## 경매 ## 경매
경매는 열림·입찰·마감 단계가 있습니다. 입찰은 로그인한 내 장수와 자원을 기준으로 저장되고, 마감은 daemon 경매는 열림·입찰·마감 단계가 있습니다. 입찰은 로그인한 내 장수와 자원을 기준으로 저장되고, 마감은 서버가 낙찰과 정산을 확정합니다. 이미 마감됐거나 자원이 바뀌면 이전에 보던 화면의 입찰 가능 상태가
worker가 낙찰과 정산을 확정합니다. 이미 마감됐거나 자원이 바뀌면 이전에 보던 화면의 입찰 가능 상태가
유효하지 않을 수 있습니다. 유효하지 않을 수 있습니다.
## 토너먼트와 베팅 ## 토너먼트와 베팅
토너먼트는 등록·진행·종료와 보상 정산 lifecycle을 가집니다. 국가 베팅도 열림과 마감·정산 event가 토너먼트는 등록·진행·종료와 보상 정산 단계을 가집니다. 국가 베팅도 열림과 마감·정산 행사가
scenario 달력에 의해 발생합니다. 각 화면의 현재 상태와 마감 시각을 확인해 주세요. profile에서 event가 시나리오 달력에 의해 발생합니다. 각 화면의 현재 상태와 마감 시각을 확인해 주세요. 서버에서 행사가
열리지 않았다면 메뉴가 있어도 참여할 수 없습니다. 열리지 않았다면 메뉴가 있어도 참여할 수 없습니다.
## 게시판·메시지 ## 게시판·메시지
@@ -55,10 +55,12 @@ scenario 달력에 의해 발생합니다. 각 화면의 현재 상태와 마감
## 기록 ## 기록
- 연감은 월 경계의 world 상태를 보존합니다. - 연감은 월이 바뀔 때의 게임 상태를 보존합니다.
- 명장·순위·왕조 화면은 현재 또는 종료된 시즌의 집계 데이터를 보여 줍니다. - 명장·순위·왕조 화면은 현재 또는 종료된 시즌의 집계 데이터를 보여 줍니다.
- 과거 플레이는 로그인한 사용자 소유 archive만 볼 수 있습니다. - 과거 플레이는 로그인한 사용자의 보관 기록만 볼 수 있습니다.
- 다른 사용자의 archive나 국가 비밀 정보는 URL을 바꿔도 공개되지 않습니다. - 다른 사용자의 개인 보관 기록과 국가 비밀 정보는 공개 범위에 따라 가려집니다.
기록 화면의 값은 실시간 메인 상태와 갱신 시점이 다를 수 있습니다. 월 정산이나 시즌 종료 직후에는 해당 기록 화면의 값은 실시간 메인 상태와 갱신 시점이 다를 수 있습니다. 월 정산이나 시즌 종료 직후에는 해당
기능의 최신 상태 표시를 함께 확인해 주세요. 기능의 최신 상태 표시를 함께 확인해 주세요.
국가 운영 판단은 [재정과 외교](./economy-and-diplomacy.md), 낯선 말은 [용어 사전](./glossary.md)에서 이어 읽으세요.
+60
View File
@@ -0,0 +1,60 @@
# 자료와 확인 범위
이 안내서는 공개 게시판의 말과 경험을 처음 참가하는 사람의 언어로 풀고,
현재 게임 규칙은 Core 코드와 대조했습니다. 조사일은 **2026-09-29**입니다.
게시판 글은 역사적 경험·제안의 근거이며, 현재 설정이나 공식 운영 규정의 증명은
아닙니다. 서버 공지와 실제 화면의 조건을 먼저 확인하세요.
## 게시판에서 읽은 자료
본문과 필요한 공개 댓글을 확인했습니다. 글 전체·작성자 정보를 복제하지 않고
주제와 링크만 남깁니다. 검색 결과의 제목만으로 전략 효과를 확정하지 않았습니다.
| 원문 | 안내서에 반영한 내용 | 읽을 때의 한계 |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------- | --------------------------------------------------------------- |
| [뉴비의 질문을 모아둡니다](https://sam.hided.net/xe/community/51784) | 직책, 훈사, 세율, 장비, 임관, 삭턴·벌점 등 초보자의 실제 질문 | 질문 모음 자체가 답이나 규칙은 아님 |
| [어떻게 해야 숙련도를 많이 높일 수 있을까?](https://sam.hided.net/xe/community/9660) | 모훈사, 양파, 징훈훈사사, 병종 저격, 준비 시간과 성장의 관계 | 2018년 계산 배수·버그 관련 조언을 현행 공략으로 재사용하지 않음 |
| [모훈사·징훈훈사사 완료 시 사기 건의](https://sam.hided.net/xe/devel/1143) | 준비 턴 약어와 자원·전쟁 준비 간의 고민 | 당시 제안이며 현재 훈사 결과를 보장하지 않음 |
| [2기 물조명성너프필수 개인열전](https://sam.hided.net/xe/community/2846) | 점사, 턴 정렬과 준비 순서가 어긋나는 사례 | 당시 참가자의 회고 |
| [10기 충차전 비스마르크 국 열전](https://sam.hided.net/xe/community/15146) | 세율 5%·지급률·포상 사용례 | 특수 기수의 재정 경험을 일반 최적값으로 취급하지 않음 |
| [43기 삼린이좋아국 국가열전](https://sam.hided.net/xe/community/25102) | 초반 확장·외교·연결 도시 수복과 보급 차단 | 당시 지도·외교·병종 조건에 의존 |
| [70기 일단박죠 이야기 마무리](https://sam.hided.net/xe/community/47834) | 모훈사출출, 양파, 발령, 전쟁 금지, 다굴 | 전술의 성공을 보편적인 보장으로 취급하지 않음 |
| [84기 유산채굴국 열전](https://sam.hided.net/xe/community/72721) | 183년 땅따 사용례 | 모든 서버의 출병 해금 연도가 183년인 것은 아님 |
| [89기 바나낫 후기](https://sam.hided.net/xe/community/75894) | 최후 2국 약속과 길막, 역할 분담 | 표현의 실제 사용례이며 외교 규정이 아님 |
| [97기 탱커단속반 장수열전](https://sam.hided.net/xe/community/78162) | 모출, 예턴, 기연, 발령, 삭턴의 대화상 의미 | 개인별 평가를 이용자 성향 정보로 재배포하지 않음 |
| [99기 99](https://sam.hided.net/xe/community/79329) | 국가 운영, NPC 정책, 세율 담당, 외교·의병·수뇌 | 최신에 가까운 회고도 현재 Core 구현과는 별도 |
| [외교 옵션을 명확하게 정의해 달라는 건의](https://sam.hided.net/xe/devel/12712) | 최후 2국 같은 포괄적 약속의 모호함 | 제안 글을 공식 규정으로 옮기지 않음 |
| [물조 명성 관련 건의](https://sam.hided.net/xe/devel/2569) | 물조와 자원 조달의 맥락 | 당시 제안의 보상 수치를 현행 규칙으로 취급하지 않음 |
| [개별 대화에서 설명한 내용을 모은 글](https://sam.hided.net/xe/devel/31537) | 댓글의 도시 장수 정렬·턴 순서 설명 | 오래된 설명이며 현재 화면별 정렬은 따로 확인해야 함 |
검색은 공개 `community`·`devel` 게시판의 제목+내용과 웹 검색에서 양파, 모출,
모훈사출출, 세율, 보급, 깃 끊기, 최후2국, 땅따 등의 표현을 조합했습니다.
전수조사나 모든 은어 수집을 뜻하지 않습니다. 다른 서버의 관습이나 사적인 대화방
내용을 섞지 않았습니다.
## 현재 규칙을 확인한 위치
다음은 문서 편집자·개발자를 위한 근거입니다. 게임을 하기 위해 코드를 읽을
필요는 없습니다. 경로는 `core2026` 저장소 기준입니다.
| 내용 | 확인 위치 |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
| 모병·훈련·사기·이동·출병의 입력과 실행 조건 | `packages/logic/src/actions/turn/general/`, 생성 커맨드 목록 |
| 출병 보호 기간 | `che_출병.ts`의 예약·실행 constraint, `packages/logic/src/constraints/misc.ts` |
| 수도와 소유 도시 연결에 따른 보급·고립 손실 | `app/game-engine/src/turn/monthlyCitySupplyAction.ts` |
| 주둔·같은 국가 장수 위치·첩보에 따른 도시 정보 | `app/game-api/src/router/world/index.ts`의 `getCurrentCity` |
| 세율 5%·30%의 내정·민심·인구 비교 | `app/game-engine/src/turn/monthlySemiAnnualAction.ts` |
| 세율에 비례하는 수입 | `packages/logic/src/economy/nationIncome.ts`, `app/game-engine/src/turn/incomeHandler.ts` |
| 적용 세율 확정 | `app/game-engine/src/turn/nationTurnMonthlyHandler.ts`, `nationTaxRate.ts` |
| 수비 설정의 표시와 선택지 | `app/game-frontend/src/views/MyPageView.vue` |
“장수 이동으로 도시 정보 밝히기”는 현재 조회·이동 코드로 확인했습니다. 직접
설명하는 게시판 원문은 이번 표본에서 확보하지 못했습니다. “모병양파”는 게시판의
양파·모병 반복 맥락을 풀어 쓴 표현입니다. 세율 30%의 효과는 계산 코드로 확인했으며,
30%를 권장하는 공략을 발견한 것으로 표시하지 않았습니다.
## 안내서를 고칠 때
새 용어는 원문에서 어떤 상황에 쓰였는지 보고, 뜻·행동·조건·손해를 함께 적습니다.
규칙 숫자는 현재 코드와 서버 설정을 확인하고, 경험담의 효율 배수나 오래된 운영
제안을 그대로 옮기지 않습니다. 화면 용어와 커뮤니티 약어가 다르면 양쪽을 연결합니다.
+18 -8
View File
@@ -2,12 +2,20 @@
## 현실 시간과 게임 달력 ## 현실 시간과 게임 달력
서버는 profile의 턴 schedule에 따라 tick을 진행합니다. 한 tick마다 실행 시각이 된 장수의 명령을 처리하고, 내 장수는 정해진 간격으로 돌아오는 차례에 행동합니다. 화면의 **다음 턴 시각**이
게임 달력이 다음 달로 넘어갈 경계에서는 월간 정산과 event도 처리합니다. 실제 턴 간격은 서버 설정에 따라 그 차례를 알려 줍니다. 서버마다 간격이 다르고 장수마다 차례가 다를 수 있으므로,
달라지므로 메인 화면의 다음 턴 시각을 확인해 주세요. 다른 사람의 시각을 내 시각으로 생각하지 마세요.
예를 들어 다음 턴이 14:03이고 턴 간격이 10분인 서버에서, 정상 진행 중
“모병 → 훈련 → 사기 진작”을 예약했다면 14:03, 14:13, 14:23에 하나씩 실행하는
식입니다. 예시의 10분은 고정 규칙이 아닙니다. 일시정지·재개나 운영 조정이 있다면
갱신된 화면의 시각을 확인합니다.
현실 시간과 별도로 게임 속 연·월이 흐릅니다. 월이 바뀔 때의 국가 정산과
내 장수의 차례는 구분하세요.
게임 달력은 1월부터 12월까지 진행하고 12월 다음은 다음 해 1월입니다. 월이 바뀔 때 수입, 외교·전쟁 상태, 게임 달력은 1월부터 12월까지 진행하고 12월 다음은 다음 해 1월입니다. 월이 바뀔 때 수입, 외교·전쟁 상태,
도시·국가 상태, 연감, scenario event 같은 여러 처리가 정해진 순서로 일어납니다. 그러므로 같은 커맨드도 도시·국가 상태, 연감, 시나리오에 정해진 사건 같은 여러 처리가 정해진 순서로 일어납니다. 그러므로 같은 커맨드도
월 변경 직전과 직후에 자원이나 조건이 달라질 수 있습니다. 월 변경 직전과 직후에 자원이나 조건이 달라질 수 있습니다.
## 예턴 ## 예턴
@@ -18,8 +26,8 @@
- 대상 도시·장수·국가, 병종, 수량 등이 필요한 커맨드는 입력값까지 저장됩니다. - 대상 도시·장수·국가, 병종, 수량 등이 필요한 커맨드는 입력값까지 저장됩니다.
- 비어 있는 칸과 실행 뒤 밀려난 끝 칸은 기본적으로 `휴식`으로 채워집니다. - 비어 있는 칸과 실행 뒤 밀려난 끝 칸은 기본적으로 `휴식`으로 채워집니다.
여러 칸에 한꺼번에 같은 명령을 넣거나 예턴을 앞뒤로 이동할 수 있습니다. 다른 탭에서 먼저 수정하면 revision 여러 칸에 한꺼번에 같은 명령을 넣거나 예턴을 앞뒤로 이동할 수 있습니다. 다른 탭에서 먼저 수정하면 저장 내용이
충돌이 날 수 있으므로 새 목록을 불러온 뒤 다시 적용해 주세요. 충돌할 수 있으므로 최신 목록을 불러온 뒤 다시 적용해 주세요.
## 예약할 수 있어도 실행에 실패할 수 있습니다 ## 예약할 수 있어도 실행에 실패할 수 있습니다
@@ -30,7 +38,7 @@
- 내 소속 국가와 직책 - 내 소속 국가와 직책
- 대상 장수·도시·국가의 존재와 외교 관계 - 대상 장수·도시·국가의 존재와 외교 관계
- 전쟁·불가침 기간과 전략 커맨드 재사용 상태 - 전쟁·불가침 기간과 전략 커맨드 재사용 상태
- scenario 연도, 가입 방식, 기술 수준과 병종 개방 - 시나리오 연도, 가입 방식, 기술 수준과 병종 개방
입력 화면은 현재 정보로 사전 판단하지만, 실제 턴에서는 전체 조건을 다시 확인합니다. 실패 이유는 명령 입력 화면은 현재 정보로 사전 판단하지만, 실제 턴에서는 전체 조건을 다시 확인합니다. 실패 이유는 명령
로그에서 확인하고 다음 예턴을 조정해 주세요. 로그에서 확인하고 다음 예턴을 조정해 주세요.
@@ -48,5 +56,7 @@
- 국가 운영자는 국고·군량, 직책과 외교 제안의 만료·수락 상태를 확인해 주세요. - 국가 운영자는 국고·군량, 직책과 외교 제안의 만료·수락 상태를 확인해 주세요.
- 경매·토너먼트·국가 베팅은 각 화면에 열린 기간과 마감 상태가 표시될 때만 입력해 주세요. - 경매·토너먼트·국가 베팅은 각 화면에 열린 기간과 마감 상태가 표시될 때만 입력해 주세요.
월간 event는 scenario resource가 결정하므로 모든 서버가 같은 월에 같은 event를 실행한다고 가정하지 월간 사건은 시나리오 설정에 따라 달라지므로 모든 서버가 같은 월에 같은 행사를 연다고 가정하지
말아 주세요. 말아 주세요.
다음은 [전쟁과 예턴 조합](./war-and-orders.md)입니다.
+73
View File
@@ -0,0 +1,73 @@
# 전쟁과 예턴 조합
도시를 공격하려면 먼저 병력을 준비하고, 공격 가능한 관계와 경로인지 확인해야
합니다. **출병**은 공격 행동, **이동**은 장수의 위치를 바꾸는 행동입니다.
이동만으로 적 도시를 점령하지는 않습니다.
## 전쟁에 처음 참가할 때
1. 국가 공지에서 공격할 도시와 방어할 도시를 확인합니다.
2. 내 위치, 모병할 병종·수량, 금과 쌀을 확인합니다.
3. 병력을 모으고 필요한 훈련·사기를 준비합니다.
4. 공격 목표와 수비 설정을 확인하고 예턴을 넣습니다.
5. 첫 전투 뒤 병력·훈련·사기·쌀과 로그를 다시 봅니다. 다음 출병도 성공할지는
앞 전투의 결과에 달려 있습니다.
병종마다 사용할 수 있는 도시·기술 등의 조건이 다릅니다. 같은 병종을 계속
모으려면 [보급과 지도](./map-and-supply.md)도 함께 봐야 합니다.
## 모출·모훈사출출은 명령 순서를 줄인 말입니다
**모**는 모병, **징**은 징병, **훈**은 훈련, **사**는 사기 진작, **출**은 출병입니다.
한 글자마다 예턴 한 칸을 뜻합니다. “모훈사출출”이라는 별도 버튼을 찾는 것이
아닙니다. 각 칸의 병종·수량·도시도 지정해야 합니다.
| 조합 | 실제 순서 | 쓰려는 목적과 주의점 |
| ---------- | ------------------------------------- | ------------------------------------------------------------------------------------- |
| 모출 | 모병 → 출병 | 급히 공격을 이어 갑니다. 훈련·사기 준비를 줄인 만큼 전투 효율을 확인해야 합니다. |
| 모출출 | 모병 → 출병 → 출병 | 공격 기회를 늘리려는 조합입니다. 첫 전투 뒤 병력이 남아야 다음 공격도 가능합니다. |
| 모훈사출 | 모병 → 훈련 → 사기 진작 → 출병 | 전투 준비 후 공격하는 기본적인 읽기 예시입니다. |
| 모훈사출출 | 모병 → 훈련 → 사기 진작 → 출병 → 출병 | 준비된 병력으로 두 차례 공격을 시도합니다. 항상 두 번 싸울 수 있다는 보장은 없습니다. |
| 징훈훈사사 | 징병 → 훈련 두 번 → 사기 진작 두 번 | 징병 뒤 준비를 길게 하는 표현입니다. 비용뿐 아니라 인구·민심과 준비 시간도 봅니다. |
| 풀훈사 | 훈련·사기를 충분히 채운 상태 | 행동 이름이 아니라 준비 상태입니다. 실제 수치를 확인합니다. |
이 조합들이 사용된 근거는 [숙련도 토론](https://sam.hided.net/xe/community/9660),
[준비 턴 건의](https://sam.hided.net/xe/devel/1143),
[70기 전쟁 회고](https://sam.hided.net/xe/community/47834)에 있습니다.
오래된 글의 효율 배수나 권장 수비 수치를 현재의 정답으로 옮기지는 않았습니다.
## 모병양파: 병력을 계속 보충하며 버티기
**양파**는 병력을 깎아도 다시 보충되어 계속 상대해야 하는 수비를 가리키는 은어입니다.
**모병양파**는 모병을 반복하면서 방어 병력을 다시 채우는 방식으로 이해하면 됩니다.
훈련·사기 준비에 시간을 쓰기보다 당장 도시가 무너지지 않게 막으려는 선택입니다.
예턴에 모병만 넣어도 자동으로 모든 공격을 막는 것은 아닙니다. 현재 도시,
병력과 자원, 수비 참여 설정·훈련사기 기준이 맞아야 합니다. 계속 모병하면 도시의
인구와 내 자원이 줄고, 준비가 덜 된 병력으로 싸우면서 손실이 커질 수 있습니다.
[숙련도 토론](https://sam.hided.net/xe/community/9660)도 양파를 개인 성장의 만능 해법으로
보지 않습니다. 국가의 급한 수비와 장수 개인의 성장 목적이 다를 수 있습니다.
처음에는 수비 수치를 임의로 낮추기보다 “지금 양파가 필요한지, 어떤 병종·수량과
수비 기준을 쓸지”를 국가에 물어보세요. 수비 설정을 바꿨다면 이후 예턴도 확인합니다.
## 점사·턴 정렬·병종 저격
**점사**는 여러 장수의 공격을 특정 도시·시점에 모으는 것, **턴 정렬**은 그 공격
순서를 맞추는 일입니다. 먼저 공격한 사람이 수비 병력을 줄이고 뒤의 사람이 성벽을
공격할 수 있으므로, 혼자 한 번 더 공격하는 것이 공동 계획과 어긋날 수도 있습니다.
[초기 전쟁 회고](https://sam.hided.net/xe/community/2846)에는 준비 턴이 어긋난 사례가 나옵니다.
**병종 저격**은 상대 병종과의 유리한 상성을 노리는 표현입니다. 장수 특기 이름인
“저격”과 문맥을 구분하세요. **수비 켬끔**은 수비 참여를 켜거나 끄며 상대할 전투를
조절하는 플레이를 뜻합니다. 처음에는 국가 지시에 맞춰 사용하고, 접속을 마칠 때
원하는 수비 설정으로 남아 있는지 확인하세요.
## 로그에서 다음 행동 결정하기
승패 한 줄만 보지 말고 누구와 싸웠는지, 병력이 얼마나 줄었는지, 쌀이 남았는지,
도시를 실제로 점령했는지 봅니다. 전투 시뮬레이터의 예상은 실제 상대 상태·난수·
연속 전투를 모두 보장하지 않습니다. 패배 후에도 내 장수가 남아 있다면 다시 준비해
참전할 수 있고, 나라가 멸망한 뒤의 소속 변경은 서버 규칙과 화면 안내를 따릅니다.
다음은 [보급·정찰·땅따](./map-and-supply.md)입니다.