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

36 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.

Game database inventory

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.