refactor: 턴 경계와 12턴 묶음을 보존하는 2배속 복구 구현

This commit is contained in:
2026-09-06 10:04:33 +00:00
parent 63da0921cb
commit d3443ec7f5
53 changed files with 1467 additions and 160 deletions
+14 -3
View File
@@ -42,7 +42,9 @@
"clock_suspension.gap_ticks",
"clock_suspension.shift_ticks",
"clock_suspension.aligned_tick",
"clock_projection_outbox.target_revision"
"clock_projection_outbox.target_revision",
"world_state.clock_recovery_start_tick",
"world_state.clock_recovery_end_tick"
],
"wallTimeFields": [
"input_event.created_at",
@@ -85,8 +87,17 @@
{
"key": "world-clock",
"policy": "REBUILD",
"authorityFields": ["world_state.clock_tick", "world_state.clock_revision"],
"projectionFields": ["world_state.clock_base_time", "world_state.clock_wall_anchor"],
"authorityFields": [
"world_state.clock_tick",
"world_state.clock_revision",
"world_state.clock_recovery_start_tick",
"world_state.clock_recovery_end_tick"
],
"projectionFields": [
"world_state.clock_base_time",
"world_state.clock_wall_anchor",
"world_state.clock_recovery_start_wall_at"
],
"owner": "game-engine/clock-operation"
},
{
+53 -27
View File
@@ -5,11 +5,9 @@
Gameplay time is an integer `GameTick`; one turn is permanently `36,000,000`
ticks. Wall time is separately authoritative for account, community, audit,
lease, retry, notification, and operational rules. It is never projected into a
game deadline. A long suspension advances the observed game coordinate to the
resume wall instant without replaying skipped complete turns, monthly events,
RNG, auctions, or tournaments. Every movable future GAME schedule is shifted by
the same tick delta. Exact alignment includes the sub-turn remainder; Gateway
maintenance preserves schedules and delegates catch-up to the engine as described below. WALL occurrences and
game deadline. A suspension keeps the normal schedule and all within-turn phases. Whole
12-turn blocks of outage are moved without executing gameplay; the remaining
one to eleven turns are executed at twice normal speed. Wall occurrences and
deadlines are outside that operation.
The clock state is stored in `world_state`:
@@ -33,7 +31,7 @@ progression.
A suspension begins under the turn-daemon fence and schema-scoped clock lock.
It records the cut tick, database wall instant, rate, source revision, and
participant checksum in `clock_suspension`. Resume reads the database wall
instant and builds an exact plan:
instant and builds a policy plan. The historical `EXACT` policy uses:
```text
gapTicks = max(0, ticksBetween(cutWall, resumeWall, rateAtCut))
@@ -42,29 +40,57 @@ alignedTick = cutTick + gapTicks
deadlineAfter = deadlineBefore + shiftTicks
```
From 2026-09-06, Gateway maintenance suspension uses `PRESERVE_SCHEDULE`.
Resume advances only the observed tick/anchor and revision; it does not move
execution cursors, general schedules, auction/message deadlines, or their
minute/second phases. The ordinary engine then processes overdue generals in
time order, bounded by one monthly boundary per pass, followed by that monthly
transition. Completed turns are not recreated.
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.
The Core product policy for long realtime downtime is now independent of turn
length: fewer than 12 overdue turns are executed normally. At 12 or more turns,
only complete blocks of 12 are skipped. For example, a 13-turn backlog shifts
schedules by 12 turns and executes the remaining turn; 23 skips 12 and executes
11; 24 skips 24. Skipping never advances gameplay years, resources, RNG, or
commands. The existing fenced backlog flush shifts the cursor and schedules
together, retaining all sub-turn phases. Explicit operator schedule movement
remains a separate action.
- 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.
This supersedes the short-lived maintenance `LEGACY_COMPLETE_TURNS` selection,
which skipped every complete suspended turn without the 12-turn policy. Legacy
policy values remain readable for existing ledgers. Unification wait, delayed
opening, and explicit `EXACT` callers keep their distinct exact alignment
contract. Applied historical ledgers are not rewritten by deployment. Both
Gateway (maintenance selection) and game engine (12-turn backlog handling) must
be deployed to activate the new behavior fully.
`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.
Before a newly leased daemon permits independent workers to advance time, it
prepares recovery under the clock lock. `turn_daemon_lease.clock_ready` starts
false and becomes true only after durable recovery and projection-worker
setup. A paused profile is durably suspended during upgrade and stays suspended
until explicitly resumed. API/worker clock reads require a live ready lease at the read revision;
RECONCILING remains fenced until the Redis outbox is applied.
Planned realtime opening rounds upward to a turn boundary. The seed CLI passes
actual wall time separately from the requested game-calendar baseline, and
Gateway publishes the stored opening anchor for both display and scheduling. Unification wait
uses `TURN_BOUNDARY`, cuts at the completed monthly cursor, and resumes at the
next normal boundary without replaying the intentional waiting period. An old
pending unification ledger is upgraded on resume; applied history stays intact.
Explicit operator movement accepts signed whole turns only. A future resume
anchor gates execution until that wall instant. Base-rate changes are rejected
while a recovery window is active.
Legacy policy values stay readable for historical ledgers. Gateway and game
profiles deploy independently: the Gateway loads the profile workspace's Prisma
connector for profiles without the recovery migration and retains the existing
`PRESERVE_SCHEDULE` maintenance behavior there. It never writes new columns or
an accelerated recovery window through that legacy model. New profiles use the
new connector and `RECOVER_TURNS`, including upgrading a pending old maintenance
ledger on resume. Applied historical operations remain unchanged. DB-preserving
DEPLOY upgrades each profile independently; other profiles need not restart.
The release manifest declares `20260906090000_add_turn_recovery_window`.
Every participant writes its `SHIFT`, `KEEP`, `REBUILD`, or `FORBID` decision,
row count, and before/after checksum to `clock_reconciliation_participant`.
+10
View File
@@ -18,6 +18,16 @@ effect; those are two facts, never one fallback clock.
`createdAt`/`updatedAt` fields remain wall audit timestamps unless this inventory
explicitly calls them game projections.
## Recovery execution rate
A turn always contains 36,000,000 GAME ticks. The configured normal duration
remains unchanged during outage recovery. A persisted boundary-to-boundary
window projects GAME time at 2x wall speed, then 1x after rejoining the normal
schedule. The window and its wall anchor belong to clock authority; they are
not gameplay deadlines or wall audit occurrences. See
[reconciliation](./game-clock-reconciliation.md) for the 12-turn skip rule,
planned-start boundaries, readiness fencing and deployment compatibility.
## Game database inventory
PREOPEN user commands are executable even though scheduled turns are stopped.