4.7 KiB
Game clock reconciliation
Product contract
Gameplay time is an integer GameTick; one turn is permanently 36,000,000
ticks. Wall time is an observation and operational-control input, never the
authority for gameplay ordering. A long suspension advances the observed game
coordinate to the resume wall instant without replaying skipped turns, monthly
events, RNG, auctions, or tournaments. Every movable future schedule is shifted
by the same exact tick delta, including the sub-turn remainder.
The clock state is stored in world_state:
clock_phasegates gameplay commits.clock_revisionidentifies the coordinate conversion generation.deadline_generationfences worker deadlines rebuilt from that generation.clock_tickandclock_wall_anchorform the durable observed-time snapshot.last_turn_tickis the execution cursor and is independent from occurrence history.
The phases are PREOPEN, RUNNING, SUSPENDED, RECONCILING, MANUAL, and
COMPLETED. PREOPEN alone permits signed negative observed ticks and floors
executable schedules at zero. RUNNING never projects below its durable tick
when wall time moves backward. SUSPENDED, RECONCILING, and COMPLETED do not
permit turn or monthly commits. MANUAL moves only through explicit engine
progression.
Durable operation
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:
gapTicks = max(0, ticksBetween(cutWall, resumeWall, rateAtCut))
shiftTicks = gapTicks - catchUpTicks
alignedTick = cutTick + gapTicks
deadlineAfter = deadlineBefore + shiftTicks
Planned maintenance, delayed opening, and unification wait use zero catch-up.
The compatibility-only complete-turn behavior is named
LEGACY_COMPLETE_TURNS; it is not the exact policy.
Every participant writes its SHIFT, KEEP, REBUILD, or FORBID decision,
row count, and before/after checksum to clock_reconciliation_participant.
The authoritative registry is
game-clock-participants.json. The
architecture gate rejects a new tick/revision field that is absent from that
inventory.
DB to Redis boundary
The database transaction leaves the phase RECONCILING and creates exactly one
clock_projection_outbox row for the target revision. An outbox worker rebuilds
auction and tournament projections and writes
sammo:{profile}:clock:active-revision last. Only after checksum verification
may the database transition to RUNNING for the same target revision and
deadline generation.
Workers must compare DB revision, Redis active revision, phase, and deadline
generation before dequeue and again in their final database transaction. Due
pop is one Redis operation: verify revision/phase, read -inf..nowTick, and
remove the claimed members. A failed Redis rebuild therefore leaves the game in
RECONCILING; process liveness alone is not readiness.
Lock order
All mutation paths use this order:
turn-daemon fencing row
-> game-clock:operation advisory transaction lock
-> general-access:persistence advisory transaction lock (only if needed)
-> world_state FOR UPDATE
-> participant rows/tables in registry order
-> DB commit
-> Redis outbox projection
The ordinary turn flush already validates phase, revision, and deadline generation after taking this lock prefix. Clock operation participants will be added without changing that prefix.
Opening invariant
Both production and direct seeding use the same scenario seeder. It stores
clock_tick = 0, last_turn_tick = 0, and the scheduled opening as
clock_wall_anchor. The metadata names seededAtWall, scheduledOpenAtWall,
projectedGameDateAtOpening, and calendarStart separately. Precreated general
turn ticks are calculated from zero and therefore cannot be negative. At the
wall anchor the in-memory phase promotion refuses any PREOPEN clock whose stored
tick is not exactly zero.
Compatibility and migration
This branch begins with dual-read defaults for callers and fixtures built before
the new columns. Database migration backfills manual profiles as MANUAL,
future anchored realtime profiles as PREOPEN, and other profiles as
RUNNING. Existing DateTime columns remain projections while tick columns are
authoritative.
Exact reconciliation stays disabled while any registry participant is
FORBID. In particular, Redis-only tournament dates and unification wait must
be moved to durable tick/revision contracts before the operation can reach
RUNNING. Removing these guards to make a partial operation pass is prohibited.