40 KiB
Time-domain inventory
This document is the authoritative classification of persistent timestamps, deadlines, cooldowns, and process-local elapsed-time rules. Classification is per rule, not per table. A feature may record both a wall occurrence and a game effect; those are two facts, never one fallback clock.
Domain contract
| Domain | Authority | Advances while suspended/reconciling | Reconciliation |
|---|---|---|---|
GAME_TIME |
world_state.clock_tick under phase/revision/generation fence |
no | SHIFT, KEEP, or REBUILD |
WALL_TIME |
PostgreSQL UTC CURRENT_TIMESTAMP for persistent decisions |
yes | never |
MONOTONIC_ELAPSED_TIME |
performance.now() / monotonic process clock |
process-local only | never persisted |
GameTick, ClockRevision, DeadlineGeneration, WallInstant, and
MonotonicDuration name these meanings in new/refactored APIs. Existing
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 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. Their authoritative processing coordinate remains signed GAME_TIME. Direct and selection-pool creation clamp only the first runnable schedule to opening tick zero; receipt audit, account sanctions, and pre-opening deletion use WALL_TIME.
| Pre-opening/account rule | Domain and authority | Reconciliation |
|---|---|---|
general.meta.prestart_delete_after |
WALL deadline from the creation InputEvent.createdAt plus the configured minimum-turn wall duration; legacy missing cutoffs use general_access_log.last_refresh once |
excluded; never shift with GAME projections |
account legacyPenalty.*.*.expire copied at general creation/possession |
WALL Unix seconds compared with the durable request receipt | excluded; an expired account sanction must not reappear when GAME dates are in the past |
| pre-opening action availability | clock_phase, then executed last_turn_tick (zero equality retained); uninitialized legacy readers may compare old date fields |
phase/cursor authority, not meta.opentime display text |
Pause means whether the rule continues to age during SUSPENDED or
RECONCILING. Projection means a non-authoritative compatibility/display
representation.
| Table / rule / field(s) | Current meaning | Domain and authority | Pause | Reconcile / projection | Decision and reason |
|---|---|---|---|---|---|
world_state.clock_tick |
observed world coordinate | GAME, self-authoritative | stop | REBUILD | root of all game-time decisions |
world_state.last_turn_tick |
executed turn cursor | GAME | stop | SHIFT | execution order must preserve skipped-turn policy |
world_state.clock_revision, deadline_generation |
clock/future-deadline generations | GAME metadata | stop | REBUILD | stale commands/workers must fail their fence |
world_state.clock_base_time, clock_wall_anchor |
tick-to-date mapping and wall observation anchor | GAME projection metadata | n/a | REBUILD | not business wall deadlines |
world_state.updated_at |
row audit | WALL, DB UTC | advance | excluded | operational history is not shifted |
clock_suspension.source_revision, target_revision, cut_tick, catch_up_ticks, gap_ticks, shift_ticks, aligned_tick |
reconciliation plan/audit | GAME metadata | stop | KEEP | immutable clock operation facts |
clock_suspension.cut_wall_at, resume_wall_at, created_at, updated_at |
operator/runtime occurrence audit | WALL, DB UTC | advance | excluded | records when the real operation occurred |
clock_projection_outbox.target_revision |
target GAME generation | GAME metadata | stop | KEEP | projection fence |
clock_projection_outbox.available_at, locked_at, applied_at, created_at, updated_at |
retry/lease/audit | WALL, DB UTC | advance | excluded | worker control cannot pause with game time |
clock_reconciliation_participant checksum/count/policy |
immutable operation evidence | GAME operation metadata | n/a | KEEP | evidence, not a deadline |
input_event.accepted_game_tick, accepted_clock_revision, accepted_deadline_generation |
daemon-claim boundary | GAME metadata | stop | KEEP across matching revision; rebase pending legacy rows only | API does not pre-stamp these; daemon owns acceptance |
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 | 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 |
general.created_at, updated_at |
entity audit | WALL, DB UTC | advance | excluded | no gameplay deadline meaning |
select_pool.reserved_until_tick |
selection reservation deadline | GAME | stop | SHIFT; reserved_until projection |
reservation is measured in game turns |
select_npc_token.valid_until_tick, pick_more_from_tick |
NPC selection windows | GAME | stop | SHIFT; DateTime projections | token is a game selection schedule; missing ticks fail closed |
general_access_log.last_refresh, last_action_at; general_access_batch.created_at; traffic_period.started_at, last_refresh; traffic_period_general.last_refresh |
traffic/access accounting | WALL, DB UTC | advance | excluded | community/operations usage, not world progression |
message.created_at_wall, delete_until_wall, tombstoned_at_wall |
envelope send/delete lifecycle | WALL, DB UTC | advance | excluded | normal messages work while the game is paused; deletion is real five minutes |
message.occurred_game_tick |
optional game context | GAME occurrence | stop | KEEP | context only, never deletion authority |
message.time, time_tick, valid_until, valid_until_tick |
rolling compatibility projections | projection only | n/a | recompute only for message_action; general-envelope values never decide lifecycle |
old columns are not fallback authority |
message_action.created_game_tick, resolved_game_tick |
action occurrence/resolution | GAME occurrence | stop | KEEP | actionable message lifecycle is separate from envelope |
message_action.expires_game_tick |
proposal response deadline | GAME | stop | SHIFT | remaining game duration survives pause |
message_action.clock_revision, deadline_generation |
response fence | GAME metadata | stop | REBUILD | stale responses are rejected |
message_action.created_at_wall, updated_at_wall; message_read_state.updated_at |
audit/read occurrence | WALL, DB UTC | advance | excluded | community UX state |
diplomacy_letter.date |
document authored/sent time | WALL, DB UTC | advance | excluded | game effect dates live in diplomacy/action state, not the document timestamp |
| diplomacy war/nonaggression start/end month data | diplomatic effect schedule | GAME calendar | stop | handled by engine schedule | affects world turns and war validity |
inheritance_point.updated_at, inheritance_log.created_at, inheritance_result.created_at, baseline/user-state audit fields |
account ledger/result audit | WALL, DB UTC | advance | excluded | account/external-currency history |
inheritance_ledger.requested_at_wall, consumed_at_wall, created_at_wall |
direct purchase receipt | WALL, DB UTC | advance | excluded | real request/debit receipt |
inheritance_ledger.applied_clock_revision, applied_deadline_generation |
game-effect fence metadata | GAME metadata | stop | KEEP | no applied_game_tick: current direct effects are timeless immediate state changes |
inheritance command input_event |
durable effect state/idempotency | WALL receipt + GAME processing fence | mixed, separated | only GAME coordinate participates | one transaction commits debit, receipt, effect, and command success; failure leaves durable input event and no debit |
auction.open_tick, auction_bid.occurred_game_tick |
open/bid game occurrence | GAME | stop | KEEP; bid event_at is projection |
event order/RNG/replay context |
auction.close_tick |
in-world close deadline | GAME | stop | SHIFT; close_at projection |
authoritative worker/finalizer deadline; missing tick fails closed |
auction_bid.requested_at_wall, created_at; auction.finalizing_at, finished_at, created_at, updated_at |
request/processing/audit | WALL, DB UTC | advance | excluded | real action and recovery history |
auction.latest_event_at |
optimistic compatibility projection of latest game event | GAME projection | stop | follows authoritative event tick | never used as wall deadline |
vote_poll.start_tick, end_tick |
poll occurrence/deadline | GAME | stop | KEEP/SHIFT; start_at/end_at projections |
poll is an in-world survey; missing deadline tick fails closed |
vote_poll.closed_at, created_at, updated_at; vote.created_at; vote_comment.created_at |
closure/user/audit occurrence | WALL, DB UTC | advance | excluded | closure receipt and community content history |
tournament nextTick, bettingCloseTick in Redis |
stage/betting deadlines | GAME | stop | REBUILD; nextAt/bettingCloseAt projections |
stages advance with the world; legacy date-only state fails closed |
| nation betting open/close year-month and tournament phase | in-world availability | GAME calendar/tick | stop | engine/Redis participant | tied to tournament turns |
| tournament/nation bet submission | user WALL request + effect at the current frozen GAME coordinate | WALL + GAME, separated | submission is allowed during SUSPENDED; GAME deadline does not age |
receipt excluded; GAME availability/fence retained | pausing stage progress must not close an already-open betting window |
nation_betting.*_at, nation_bet.*_at |
user/audit occurrence | WALL, DB UTC | advance | excluded | receipts, not close authority |
game_history.date, old-general turntime, archived projected dates |
archived game-calendar projection | GAME historical display | stop | KEEP, never shifted | immutable archive/replay record |
archive/entity created_at, cancellation opened_at/cancelled_at, unification completed_at |
operation/archive audit | WALL, DB UTC | advance | excluded | real creation/completion facts |
general_turn_revision.lease_expires_at, nation_turn_revision.lease_expires_at and audit timestamps |
edit lease/revision audit | WALL, DB UTC | advance | excluded | editor concurrency timeout |
| board post/comment, log/error/event, legacy migration timestamps | content/audit/migration history | WALL, DB UTC | advance | excluded | community and operational evidence |
The year-9999 message sentinel remains a legacy projection only.
MAX_SAFE_GAME_TICK is the separate GAME-domain infinite sentinel. Neither is
converted into or used as the other domain's ordinary deadline.
Gateway database inventory
Gateway has no gameplay clock authority. Every Gateway DateTime is WALL_TIME:
app_user: identity/session/icon/terms/privacy/Kakao/grace/deletion/login andcreated_at/updated_atfields.- access grants, retired identities, admin audits, user icons, legacy member logs, and migration timestamps.
- profile lifecycle
preopen_at,open_at,scheduled_start_at, build request, start/completion/last-used, and row audit timestamps. These are real control plane schedules; they do not replace a profile'sworld_state.clock_tick. - subscriptions/preferences/receipts/notifications and web-push delivery
available_at,locked_at,delivered_at, expiration and audit fields. - runtime actions, operations, releases, and bulk releases: schedule, start, completion, retry, lease, heartbeat, successful and audit timestamps.
Competitive Gateway operation/release claim and lease renewal decisions read
PostgreSQL CURRENT_TIMESTAMP inside the persistence transaction; the caller's
poll timestamp is not authoritative. A Gateway PREOPEN wall schedule is an operational request; once
the game exists, gameplay schedules use the game database tick.
Process-local monotonic inventory
The following are MONOTONIC_ELAPSED_TIME and are never persisted: daemon/RPC
wait budgets, turn processing budgets, worker poll/resync intervals, lock
acquisition waits, readiness loops, short-lived cache TTLs, latency metrics, and
test wait loops. Production implementations use performance.now() where an
elapsed duration is measured. Date.now()/new Date() remains valid only when
creating or formatting a WALL occurrence, calculating a non-competitive auth
TTL for an external protocol, or providing an explicit test clock.
API phase policy
| Operation | During SUSPENDED / RECONCILING |
Fence |
|---|---|---|
| normal public/private/nation message send/read/delete, including receiving/reading an existing recruitment letter envelope | allowed | WALL DB transaction only; actionable deadline remains frozen GAME state |
| notification/account/inheritance history/audit reads | allowed | WALL |
| actionable message response | rejected/queued except the explicitly authorized unification response | daemon clock phase/revision/generation |
| tournament/nation bet submission while its GAME window is open | allowed in SUSPENDED; rejected in RECONCILING |
frozen GAME deadline + phase/revision/generation fence |
| tournament stage transition/close/settlement and nation-bet close/settlement | not applied | daemon GAME fence |
| turn, reservation, war/diplomacy effect, auction, vote, other tournament mutation | not applied | daemon GAME fence |
| direct inheritance state mutation | rejected if the daemon GAME fence cannot commit | atomic input-event + revision/generation fence |
There is currently no account-only inheritance purchase endpoint. Direct
inheritance commands use policy 1 (no debit if game mutation cannot commit), not
an ambiguous partially-applied state. Their InputEvent.requestId is the durable
idempotency/effect state; InheritanceLedger.requestId proves the one successful
receipt.
loadCurrentGameTime call-site audit
All production call sites were reviewed. The remaining uses are GAME-only:
messages/store: create/read/invalidate the separatemessage_action; normal envelope creation, display, read state, and deletion do not load game time.messages/diplomaticResponse: game-effect/log calendar context after an actionable response fence; the letter's authored date is DB WALL_TIME.- auction
open,scheduler,worker, and router: open/close tick projection, due evaluation, and fence context; bid receipt time is separate WALL_TIME. - vote router, tournament router/worker: GAME poll/stage deadlines and Redis projection fences.
- troop/general/join selection routers: current world turn/schedule context;
the NPC reservation mutation holds the clock advisory lock and
world_staterow fence before reading it. - lobby: display-only projected server game time and phase.
No inheritance receipt, ordinary message timestamp/delete rule, audit log, lease, outbox retry, API timeout, or worker budget calls this helper.
Ordinary message send/read-state/delete mutations retain their durable API
InputEvent transaction and read-model journal, but use the WALL-only input
boundary. That boundary deliberately does not acquire the game clock advisory
lock, so a reconciliation transaction cannot unnecessarily serialize community
messaging. Actionable responses continue to use the GAME-fenced boundary.
Migration boundary
Migration 20260903140000_split_message_wall_and_game_time adds and backfills
the explicit message, action, auction-bid, inheritance receipt, and selection
cooldown authorities. It never extends an already-expired message delete window.
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.
WALL lease precision and query parameters
Gateway operation/release lease recovery rechecks expiration in the database
under the existing claim lock. It does not compare a JavaScript Date to the
original timestamptz(6) value: that round trip loses microseconds and can prevent
expired jobs from being reclaimed. Owner fences and renewal checks remain active.
Read-model outbox dispatch casts an injected WALL timestamp explicitly before
subtracting a retry interval; production still defaults to the database WALL clock.