시간 작동 방식 변경 #1
@@ -70,6 +70,13 @@ as the archive, prompts, and final unification state. Only a `raiseInvader`
|
||||
message response tied to that active suspension may pass the suspended command
|
||||
queue; all other gameplay remains pending.
|
||||
|
||||
If the profile processes are operationally stopped during this wait, Gateway
|
||||
RESUME starts the runtime without consuming the suspension. The database remains
|
||||
`SUSPENDED`; the daemon-authorized response is still the only transition that may
|
||||
reconcile it. Selecting one difficulty resolves every pending `raiseInvader`
|
||||
alternative created at the same game tick, while preserving each message's wall
|
||||
envelope as history.
|
||||
|
||||
The response transaction verifies daemon authority, performs the exact
|
||||
alignment, applies all participant shifts, optionally changes the turn rate,
|
||||
then creates the invader nation, deterministic general IDs/RNG results, first
|
||||
@@ -77,7 +84,8 @@ turns, and the final target-revision outbox. The optional rate change refreshes
|
||||
the outbox with the final base/rate before commit. DB remains `RECONCILING`
|
||||
until the daemon projection worker applies Redis and verifies the target
|
||||
revision/generation. Games without an invader choice move directly to
|
||||
`COMPLETED`.
|
||||
`COMPLETED`. After an invader game reaches `isUnited=3`, `InvaderEnding` also
|
||||
changes the clock to `COMPLETED` in the terminal monthly transaction.
|
||||
|
||||
## DB to Redis boundary
|
||||
|
||||
@@ -163,6 +171,12 @@ it never changes the rule to WALL_TIME. A WALL rule likewise never derives an
|
||||
authority tick. See [`time-domains.md`](./time-domains.md) for the complete
|
||||
inventory and migration policy.
|
||||
|
||||
Migration `20260903183000_turn_daemon_lease_utc_wall` expires ephemeral daemon
|
||||
leases at deployment and installs UTC wall defaults. Migration
|
||||
`20260903201500_complete_invader_game_clock` repairs legacy `isUnited=3` worlds
|
||||
left in a running/manual phase and resolves obsolete unchosen invader actions at
|
||||
the terminal authoritative game tick.
|
||||
|
||||
No active participant remains `FORBID`. Tournament writes carry
|
||||
tick/revision/generation coordinates and are revision-fenced in Redis.
|
||||
Unification wait uses the same durable ledger and outbox boundary; the former
|
||||
|
||||
@@ -44,6 +44,12 @@ Redis 투영은 DB commit 뒤 action ID로 멱등 적용됩니다.
|
||||
phase/revision/generation을 다시 잠가 검증합니다. 단계 전환·마감·정산은 실행하지
|
||||
않으며, 원자적 reconciliation이 진행되는 `RECONCILING`에서는 새 베팅도 받지 않습니다.
|
||||
|
||||
daemon lease는 게임 schedule이 아니라 운영 `WALL_TIME`입니다. 게임 DB session이
|
||||
`Asia/Seoul`이어도 acquire/renew/assert/release는 모두
|
||||
`CURRENT_TIMESTAMP AT TIME ZONE 'UTC'`를 사용합니다. 따라서 정지한 게임의
|
||||
lease도 현실 시간에 만료되며, KST session에서 UTC `timestamp without time zone`
|
||||
column을 9시간 미래로 쓰지 않습니다.
|
||||
|
||||
## 중단 후 재개
|
||||
|
||||
realtime daemon은 재개할 때 Ref `checkDelay()`와 같은 한도를 적용합니다.
|
||||
@@ -60,6 +66,14 @@ realtime daemon은 재개할 때 Ref `checkDelay()`와 같은 한도를 적용
|
||||
장수 턴 tick은 바꾸지 않습니다. 동시에 `clock_wall_anchor`를 작업 실행
|
||||
시각으로 다시 고정합니다.
|
||||
|
||||
통일 후 이민족 선택을 기다리는 `UNIFICATION_WAIT`는 일반 maintenance 재개와
|
||||
다릅니다. 운영상 STOP된 profile을 RESUME하면 Gateway는 프로세스만 다시 띄우고
|
||||
게임 clock은 `SUSPENDED`로 유지합니다. daemon이 수신자 소유권과 command fence를
|
||||
확인한 `raiseInvader` 응답만 suspension을 원자적으로 reconciliation할 수 있습니다.
|
||||
한 난이도를 수락하면 같은 통일 tick의 다른 선택지는 모두 resolved 처리됩니다.
|
||||
이민족전이 끝나 `isUnited=3`이 되면 clock phase도 같은 월 transaction에서
|
||||
`COMPLETED`가 되어 이후 GAME_TIME이 진행하지 않습니다.
|
||||
|
||||
DB migration은 GAME 규칙의 기존 DateTime 투영에서 tick을 채웁니다. 새 설치와 migration
|
||||
재실행은 `prisma:migrate:deploy:game`으로 수행합니다. 메시지의 연도 9999 같은
|
||||
무기한 호환값은 일반 메시지의 투영일 뿐입니다. actionable deadline은
|
||||
|
||||
@@ -222,6 +222,11 @@ claim 가능한 상태에서 attempts를 증가시켜 재처리합니다.
|
||||
5. `EngineStateManager`가 in-memory mutation과 transaction flush를 묶습니다.
|
||||
6. `TurnDaemonLifecycle`이 control queue와 schedule을 실행합니다.
|
||||
|
||||
`turn_daemon_lease`는 운영 WALL_TIME입니다. 모든 write와 active 비교는 DB
|
||||
session timezone과 무관하게 UTC wall expression을 사용합니다. Lease migration은
|
||||
profile process가 멈춘 배포 경계에서 기존 ephemeral row를 만료시켜 구버전
|
||||
writer가 만든 KST timestamp도 새 daemon의 fresh fencing epoch를 막지 않습니다.
|
||||
|
||||
Lifecycle은 가장 빠른 장수 턴과 다음 tick 중 앞선 시각을 선택합니다.
|
||||
pause gate, 수동 run, shutdown과 budget을 같은 loop에서 처리합니다.
|
||||
|
||||
@@ -290,6 +295,12 @@ event catalog, in-memory state, dirty marking, flush와 reload 검증까지
|
||||
- Worker timeout은 요청 실패로 반환하며 DB commit 여부를 별도로 확인합니다.
|
||||
- API·daemon process 재시작은 `InputEvent`, lease, checkpoint와 operation
|
||||
상태에서 이어집니다.
|
||||
- `UNIFICATION_WAIT` 중 operational STOP/재시작은 profile process만 복구하며
|
||||
suspension을 일반 RESUME으로 소비하지 않습니다. 선택 응답의 daemon fence가
|
||||
clock alignment와 이민족 생성을 함께 commit합니다.
|
||||
- test-only `ManualClock`과 `StepClock`의 sleep은 논리 시간을 즉시 전진한 뒤
|
||||
event loop에 한 번 양보합니다. terminal/suspended polling이 timer 기반
|
||||
shutdown과 test timeout을 굶기지 않아야 합니다.
|
||||
- 운영 process와 외부 Caddy 상태는 local build·mock E2E로 증명되지 않습니다.
|
||||
|
||||
## Build와 배포
|
||||
|
||||
@@ -40,7 +40,7 @@ representation.
|
||||
| `input_event.processing_game_tick`, `processing_clock_revision`, `processing_deadline_generation` | actual mutation boundary | GAME metadata | stop | KEEP | effect validation/RNG uses this coordinate |
|
||||
| `input_event.created_at`, `processing_at`, `completed_at`, `lease_until` | request receipt, processing audit, lease | WALL, DB UTC | advance | excluded | external occurrence and worker lease |
|
||||
| `read_model_outbox.*_at`, `web_push_outbox.*_at` | availability, claim, delivery, audit | WALL, DB UTC | advance | excluded | retry and notification delivery are operational |
|
||||
| `turn_daemon_lease.lease_until`, `heartbeat_at` | daemon liveness | WALL, DB UTC | advance | excluded | a paused game must still lose a dead daemon lease |
|
||||
| `turn_daemon_lease.lease_until`, `heartbeat_at` | daemon liveness | WALL, DB UTC | advance | excluded | acquire/renew/assert/release use `CURRENT_TIMESTAMP AT TIME ZONE 'UTC'`, including KST sessions |
|
||||
| `general.turn_tick` | next general turn | GAME | stop | SHIFT; `turn_time` projection | determines engine order |
|
||||
| `general.recent_war_tick` | past battle occurrence | GAME | stop | KEEP; `recent_war_time` projection | historical event does not move |
|
||||
| `general.meta.next_change_tick` | N-turn reselection cooldown | GAME | stop | SHIFT; `next_change`/`nextChangeAt` projections | expressed in turns; missing tick fails closed |
|
||||
@@ -164,3 +164,12 @@ Old projection columns remain during rolling deployment, but new code never
|
||||
chooses a clock by NULL fallback: GAME rules require their tick; WALL rules use
|
||||
their wall column. The disposable migration verifier covers populated upgrade,
|
||||
indexes/constraints, replay safety, and a second no-op deploy.
|
||||
|
||||
Migration `20260903183000_turn_daemon_lease_utc_wall` treats daemon leases as
|
||||
ephemeral WALL authority: it expires pre-deployment rows, changes the heartbeat
|
||||
default to DB UTC, and requires the new daemon to acquire a fresh fencing epoch.
|
||||
Migration `20260903201500_complete_invader_game_clock` moves historical
|
||||
`isUnited>=3` worlds from `RUNNING`/`MANUAL` to `COMPLETED` and resolves pending
|
||||
unchosen invader actions at `max(created_game_tick, world_state.clock_tick)`.
|
||||
Neither migration shifts a WALL occurrence or turns one rule into a clock
|
||||
fallback.
|
||||
|
||||
@@ -27,6 +27,8 @@ chain을 적용하고 두 번째 실행은 `No pending migrations to apply`여
|
||||
- `world_state`, `nation`, `city`, `general`, `message`, `troop`
|
||||
- `general_turn`, `nation_turn`과 revision·lease field
|
||||
- `input_event`, `turn_daemon_lease`
|
||||
- KST session에서도 `turn_daemon_lease` acquire/renew/release가 DB UTC wall
|
||||
deadline을 기록하며, migration 시 기존 ephemeral lease가 만료되는지
|
||||
- `read_model_revision`, `read_model_outbox`, `read_model_revision_meta`, `web_push_outbox`
|
||||
- 두 outbox의 `available_at`, `locked_at`, `delivered_at`, `created_at`은
|
||||
millisecond 정밀도 `timestamp without time zone`을 유지한다. 이 migration
|
||||
@@ -61,6 +63,19 @@ pnpm --filter @sammo-ts/infra verify:migration:time-domains
|
||||
검증이 끝나면 이름을 직접 확인한 임시 database와 role만 제거합니다. 공유
|
||||
database나 Compose volume을 삭제하지 않습니다.
|
||||
|
||||
## Daemon lease와 이민족 종료 upgrade
|
||||
|
||||
`20260903183000_turn_daemon_lease_utc_wall`은 `timestamp without time zone`
|
||||
lease를 DB UTC 기준으로 통일하고 배포 시 기존 lease를 만료시킵니다. 이는
|
||||
영속 gameplay history가 아니라 재획득 가능한 운영 authority이므로 새 daemon이
|
||||
fresh fencing epoch를 얻는 것이 backfill 계약입니다.
|
||||
|
||||
`20260903201500_complete_invader_game_clock`은 이미 `isUnited=3`인데
|
||||
`RUNNING`/`MANUAL`인 legacy world를 `COMPLETED`로 바꾸고, 더 이상 선택할 수 없는
|
||||
pending `raiseInvader` action을 terminal game tick에 resolve합니다. 메시지
|
||||
`created_at_wall`, `delete_until_wall`과 기타 WALL_TIME envelope 값은 변경하지
|
||||
않습니다.
|
||||
|
||||
## Game outbox UTC-wall populated upgrade 검증
|
||||
|
||||
Git에서 제외된 전용 PostgreSQL URL을 주입해 target 직전 migration chain부터
|
||||
|
||||
Reference in New Issue
Block a user