- Updated pnpm-workspace.yaml to enforce TypeScript version 6.0.2. - Replaced legacy TypeScript import with the current version in comparison tools. - Added ignoreDeprecations setting in tsconfig.base.json to suppress deprecation warnings for baseUrl. - Introduced TypeScript version policy documentation outlining the rationale and upgrade gate for moving to TypeScript 7.
11 KiB
Runtime and Build Profiles
Build outputs should be emitted to /dist/{profileName} per profile to keep
deployments predictable. Profiles are server+scenario pairs, and scenario
selection is required because it drives unit sets and DB settings.
TypeScript Toolchain
The rewrite workspace uses exactly TypeScript 6.0.2. TypeScript 7 is not a
supported build or validation environment until the compiler/tooling APIs used
by this repository and the dependent toolchain are available and compatible.
Package-local TypeScript 5 or 7 overrides are not allowed in app/*,
packages/*, or tools/*. The legacy runtime under legacy/ is maintained
separately and is outside this rule.
The rationale, scope, and TypeScript 7 upgrade gate are defined in
typescript-version.md.
Database Schemas (Gateway vs Game)
Gateway uses a shared schema (default public) for login/profile state, while
each game profile runs against its own schema. This keeps gateway data stable
and allows profile-scoped game data resets.
- Gateway schema:
GATEWAY_DB_SCHEMA(defaultpublic) - Game schema:
PROFILEvalue (e.g.,hwe,che) - Optional override for gateway DB URL:
GATEWAY_DATABASE_URL
Suggested Build Pattern
- Wrapper script under
tools/build-scripts pnpm build:server --profile che --scenario default- CI-friendly:
PROFILE=che SCENARIO=default pnpm build:server - Build tooling: use
tsdownfor backend/libs, and Vite for frontend apps.
Deterministic RNG Policy
- Gameplay randomness must be reproducible from a deterministic seed
- Prefer
legacy/hwe/ts/util/LiteHashDRBG.tsandlegacy/hwe/ts/util/RNG.ts - Seed composition should include hidden base seed plus action context
Gateway Orchestration (Single Host Draft)
Gateway API is the single source of truth for profile state and reconciles
PM2-managed processes on boot and on a short interval. The orchestrator runs
as a separate process (GATEWAY_ROLE=orchestrator). The DB owns the desired
state; PM2 is treated as the actuator. The runtime state is grouped so that
game-api + turn-daemon are either on together or off together.
DB-Owned Profile State
Profiles are tracked by profileName (= ${profile}:${scenario}).
Gateway loads the profile table on boot, then reconciles PM2 to match.
예약됨(RESERVED): preopen/open timestamps are set; processes off.가오픈(PREOPEN): build done; API+daemon on, daemon paused.가동중(RUNNING): bothgame-apiandturn-daemonshould be on.정지됨(STOPPED): processes off; may be resumed later.정지(오류)(PAUSED): fatal error; daemon paused but process remains on.천하통일(COMPLETED): game finished; API on, daemon paused.비활성화(DISABLED): excluded from orchestration; start forbidden.
Boot Reconciliation
- Load profile rows from DB.
- List PM2 processes and map
profileName -> running state. - For each profile:
- If desired
RUNNING/PREOPEN/PAUSED/COMPLETEDand any process is missing, start. - If desired
RESERVED/STOPPED/DISABLEDand any process is running, stop both.
- If desired
- Persist errors to DB for audit.
Internal Scheduler (Gateway Cron)
Gateway runs a lightweight cron loop (setInterval) that:
- When
RESERVEDandpreopenAt <= now, queue a build for the reserved commit. - When build succeeds, status becomes
PREOPEN(daemon paused). - When
openAt <= now, status becomesRUNNINGand daemon resumes. - Optionally drains a build queue (see build workflow).
Build Workflow (Admin)
- Admin triggers a build request for a profile.
- Gateway queues a build job with
(profileName, commitSha)and prepares a per-commit workspace (/.worktrees/{commitSha}by default). - Workspace is backed by
git worktreeand is reused across builds for the same commit. - Each workspace stores
lastUsedAtin DB so cleanup can remove stale worktrees. - Cleanup is invoked manually by admin API and removes worktrees unused for 6+ months.
- Build runs
pnpm installwhen workspace is created, then executespnpm --filter @sammo-ts/game-api buildandpnpm --filter @sammo-ts/game-engine build, then marks build success/failure. - On success, status moves to
PREOPENfor reserved builds or stays unchanged for manual builds.
Current Implementation Status
- Turn daemon lifecycle + in-memory state live in
app/game-enginewith DB flush hooks. - 모든 tRPC mutation은 PostgreSQL
input_event경계에서 request ID와 처리 상태를 기록한다. - 월드 mutation과 daemon control은 PostgreSQL inbox로 전달된다. Redis는 더 이상 이 명령들의 영속 큐가 아니며 realtime/battle-sim 전송에만 남아 있다.
- API가 직접 변경하는 예약 턴·메시지 등의 DB 쓰기는 API input event 완료와 같은 transaction에서 commit된다.
- 엔진 명령은 도메인 DB 쓰기, in-memory world flush, 예약 턴, 로그, 결과 저장과 inbox 완료를 하나의 transaction으로 commit한다.
Turn Daemon and API Server Behavior (Outline)
- Turn daemon responsibilities: scheduling, turn resolution, state persistence
- API server responsibilities: query/command intake, validation, response shaping
- Concurrency model between daemon and API server
- Durable command channel: PostgreSQL
input_event - Client updates: SSE between API server and frontend where appropriate
Durable Input Event Split
PostgreSQL is the source of truth for accepted input. Redis pub/sub remains a best-effort notification/fan-out path and must not decide whether a gameplay mutation was committed.
Recommended Split
- PostgreSQL
input_event:- API mutation acceptance, idempotency, attempts and audit state.
- API server -> daemon mutation/control payloads and durable results.
FOR UPDATE SKIP LOCKEDclaim plus worker lease for concurrent consumers.- engine result and gameplay state commit in the same transaction.
- Redis pub/sub:
- Daemon -> API server: low-stakes live update signals (run started/ended).
- API server -> frontend: SSE fan-out triggered by pub/sub updates.
- Do not use pub/sub for data that must be replayed or audited.
Operational Notes
- 각 profile은 별도 game DB schema/connection을 사용한다.
- HTTP
Idempotency-Key(없으면 Fastify request ID)와 tRPC path를 합친 값이 API input event key다. - 처리 중 lease가 만료된 engine event만
PENDING으로 회수한다. - 완료 event 보존/정리 기간과
FAILED재처리 운영 정책은 별도로 정해야 한다.
Detailed lifecycle and control flow are defined in
docs/architecture/turn-daemon-lifecycle.md.
Authentication and Session Management (Draft)
Login uses Kakao OAuth as the primary identity provider because it leverages Korean real-name verification and helps prevent multi-account abuse. The system also supports local ID/password login for users who cannot use Kakao.
Login Options
- Kakao login button (OAuth flow via Gateway).
- Local login with ID/password (managed by Gateway).
- Passkey is a possible future option; define later if required.
- Auto-login should be supported when an active session exists.
Session and SSO-Like Behavior
- Gateway handles login and owns primary sessions in Redis.
- Game servers may run different branches; treat Gateway as a central SSO authority that issues session tokens for each server+scenario profile.
- API servers validate tokens against Redis and accept sessions issued by Gateway without re-authentication.
- Session tokens should be scoped by server+scenario profile to avoid cross-server leaks.
Operational Notes
- Prefer HTTP-only secure cookies for session tokens where possible.
- Provide a logout flow that revokes tokens in Redis.
- Track last-login and session metadata for audit and abuse detection.
Engine Runtime Flow (Draft)
Turn Daemon Loop
- The turn daemon runs as a single-threaded loop.
- The daemon engine uses in-memory state as the primary working set.
- The daemon waits on two conditions during the event loop.
- Query/command requests from the external API server.
- The scheduled start time of the next turn.
- External requests are processed until the next turn start time is reached.
- If no requests arrive, the daemon waits until the next turn start time.
- When the next turn start time arrives, the daemon starts turn processing immediately even if requests remain queued.
- While the daemon is resolving a turn, the API server queues incoming requests.
현재 구현은 control 명령과 registry의 모든 world mutation을 같은 DB queue로 읽어 턴 사이에 처리한다.
Daemon Control Contract (Draft)
API server commands are inserted into PostgreSQL input_event; the daemon
stores status/results on the same row. An in-process queue remains available
for tests and internal signals.
export type RunReason = 'schedule' | 'manual' | 'poke';
export type DaemonCommand =
| { type: 'run'; reason: RunReason; targetTime?: string; budget?: TurnRunBudget }
| { type: 'pause'; reason?: string }
| { type: 'resume'; reason?: string }
| { type: 'getStatus'; requestId: string };
export type DaemonEvent =
| { type: 'status'; requestId?: string; status: TurnDaemonStatus }
| { type: 'runStarted'; at: string; reason: RunReason }
| { type: 'runCompleted'; at: string; result: TurnRunResult }
| { type: 'runFailed'; at: string; error: string };
API Server Flow
- The API server validates commands and records every tRPC mutation in PostgreSQL.
- After a request is processed, the API server returns the result to clients.
- Read-only queries may access the DBMS directly.
- The API server may use SSE to stream live updates to the frontend.
Queue and Rate Limits
- Engine-owned requests are delivered through the PostgreSQL inbox.
- Per-user pending limits and event retention remain operational follow-up work; idempotency and exclusive claim are implemented.
In-Memory and DBMS Flush
- The daemon processes actions against in-memory state by default.
- DBMS writes are flushed in bulk after turn processing completes.
- Frequently changing "next-turn intent" data is stored separately.
- The API server persists this data in the DBMS.
- The daemon loads only this data when the next turn begins.
Turn Daemon vs API Query Priority (Outline)
- Expected priority order under load
- Rules for preemption or deferral
- Handling of write-heavy operations during turn resolution
In-Memory Processing and DBMS Flush (Outline)
- When in-memory state is authoritative
- Flush checkpoints and transactional boundaries
- Recovery strategy after crash during flush
Testing and Observability (Outline)
- Metrics and logs required to validate scheduling and flush behavior
- Suggested test scenarios for concurrency and consistency
Game Logic Testing (Draft)
Deterministic Inputs
- RNG seed composition (hidden server seed, turn info, general info).
- Scenario selection and scenario data.
- Trigger set inputs: nation, general, and city state.
- Game time and tick schedule.
Recommended Unit Test Flow
- Prepare a deterministic test fixture (mock DB or in-memory state snapshot).
- Execute game logic unit tests with fixed inputs and seeds.
- Compare expected outputs against the pre-flush change set that would be written to the DBMS.
Notes
- Deterministic RNG makes output comparison stable and repeatable.
- Prefer snapshotting inputs/outputs so regressions are easy to track.