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