Files
core2026/docs/architecture/time-domains.md
T

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 and created_at/updated_at fields.
  • 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's world_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 separate message_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_state row 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.