Files
core2026/docs/architecture/runtime.md
T
Hide_D 2cb6bc8e96 chore: standardize TypeScript version to 6.0.2 across workspace
- 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.
2026-07-25 06:39:04 +00:00

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 (default public)
  • Game schema: PROFILE value (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 tsdown for 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.ts and legacy/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): both game-api and turn-daemon should 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

  1. Load profile rows from DB.
  2. List PM2 processes and map profileName -> running state.
  3. For each profile:
    • If desired RUNNING/PREOPEN/PAUSED/COMPLETED and any process is missing, start.
    • If desired RESERVED/STOPPED/DISABLED and any process is running, stop both.
  4. Persist errors to DB for audit.

Internal Scheduler (Gateway Cron)

Gateway runs a lightweight cron loop (setInterval) that:

  • When RESERVED and preopenAt <= now, queue a build for the reserved commit.
  • When build succeeds, status becomes PREOPEN (daemon paused).
  • When openAt <= now, status becomes RUNNING and 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 worktree and is reused across builds for the same commit.
  • Each workspace stores lastUsedAt in DB so cleanup can remove stale worktrees.
  • Cleanup is invoked manually by admin API and removes worktrees unused for 6+ months.
  • Build runs pnpm install when workspace is created, then executes pnpm --filter @sammo-ts/game-api build and pnpm --filter @sammo-ts/game-engine build, then marks build success/failure.
  • On success, status moves to PREOPEN for reserved builds or stays unchanged for manual builds.

Current Implementation Status

  • Turn daemon lifecycle + in-memory state live in app/game-engine with 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.

  • PostgreSQL input_event:
    • API mutation acceptance, idempotency, attempts and audit state.
    • API server -> daemon mutation/control payloads and durable results.
    • FOR UPDATE SKIP LOCKED claim 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.
  • 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.