중단 후 대기와 2배속 복구 경계 및 전체 공지 적용

This commit is contained in:
2026-09-07 14:37:55 +00:00
parent d57fd3e553
commit c39527bdd7
12 changed files with 541 additions and 69 deletions
+44 -21
View File
@@ -40,30 +40,53 @@ alignedTick = cutTick + gapTicks
deadlineAfter = deadlineBefore + shiftTicks
```
From 2026-09-06, maintenance and crash recovery use `RECOVER_TURNS`.
This supersedes the earlier same-day `PRESERVE_SCHEDULE` immediate catch-up
policy. The base turn length does not change. One turn remains 36,000,000
ticks; a persisted `TurnRecoveryWindow` changes only the wall execution rate.
From 2026-09-07, maintenance and crash recovery use the following
`RECOVER_TURNS` policy. The base turn length does not change. One turn remains
36,000,000 ticks; a persisted `TurnRecoveryWindow` changes only wall execution.
- Count complete overdue turns from the durable observation to the normal
timeline. Skip only `floor(overdueTurns / 12) * 12` turns, moving future
schedules and the execution cursor by the same integer delta.
- Execute the sub-turn remainder immediately through the ordinary engine.
Reach the next normal turn boundary at normal speed, then execute the
remaining one to eleven turns of backlog at 2x speed.
- Join the original schedule at the recorded end boundary and return to 1x.
Four hours of backlog on a 60-minute server needs four hours at 2x; it runs
eight turns in that time. Resources, RNG, commands and monthly handlers run
normally for those turns, in the existing chronological order.
- Purchased within-turn offsets remain logical offsets. Their wall offsets
compress during recovery and return to the original minutes/seconds after
the end boundary. The API and browser expose the recovery interval.
- An entire delay strictly below `min(600 seconds, turnSeconds / 10)` catches
up immediately through the ordinary engine. Equality uses recovery. The
limit is 30 seconds on a 5-minute server and 6 minutes on a 60-minute server.
- For longer delays, skip only complete 12-turn blocks, moving future
schedules and the execution cursor by the same integer delta. Never apply
the short-delay exception again to the remainder. An exact multiple of
12 turns needs no acceleration window.
- Preserve the remaining observation, including its partial-month position.
Wait without advancing, then execute at 2x until joining the original
schedule at a logical month boundary. There is no initial 1x segment or
fractional immediate burst in a newly planned window.
- Let `S` be the observation after skips, `N` the normal tick at resume,
`T` one turn, and `r` ticks per second. Choose
`E = ceil((2*N-S)/T)*T`. The end is `resume + (E-N)/r` and the wait is
`(E+S-2*N)/(2*r)`. End wall time and 2x duration round up to milliseconds;
derive the start by subtraction. This preserves the end boundary with at
most one millisecond of wall resolution error.
- On a 60-minute server stopped at game 00:10 and resumed at wall 00:24,
wait until 00:35, then run 2x until game/wall 01:00. A 240-minute outage
from the same stop waits from 04:10 to 04:35 and rejoins at 09:00.
- Resources, RNG, commands and monthly handlers execute in the existing
chronological order. Purchased within-turn offsets remain logical offsets;
their wall offsets compress during recovery and return to normal afterward.
`clock_recovery_start_tick`, `clock_recovery_end_tick`, and
`clock_recovery_start_wall_at` are an all-or-none durable window. Flush/reload
preserves it; a short restart reuses it. A new long outage replans against the
original normal timeline. Game-date epoch and wall epoch may differ; never
convert the real wall instant using `dateToTick` to compute normal time.
`clock_recovery_start_wall_at` are an all-or-none durable window. The migration
`20260907150000_wait_then_turn_recovery` permits partial-month starts and keeps
end-boundary and safe-integer constraints. Existing windows remain valid and
retain their old pre-start 1x behavior; new windows use the stored tick as the
frozen lower bound and `clockWallAnchor` as the future start gate. Do not roll
back to code requiring whole-turn starts while a new window is stored.
Flush/reload preserves the window; short restarts reuse it. A new long outage
replans against the original normal timeline. Game-date epoch and wall epoch
may differ; never use `dateToTick(realWallInstant)` to compute normal time.
The API and browser expose waiting, start and end boundaries without needing
a fresh response at the speed transitions.
Projection completion stores one public system message with both recovery
wall dates (Korean time) and ISO interval metadata. Message creation, mailbox
read-model invalidation, and the `RUNNING`/outbox `APPLIED` transition share a
PostgreSQL transaction. Retries after Redis application cannot duplicate the
message. Existing read-model outbox delivery refreshes connected clients.
Before a newly leased daemon permits independent workers to advance time, it
prepares recovery under the clock lock. `turn_daemon_lease.clock_ready` starts