Skip to content

Domain code does not read the clock: every server step has one time ​

Status: Accepted (2026-11-05)

Context ​

When every part of the server reads the system clock for itself, two checks of one packet see two different times, and a comparison at the edge of a window depends on which check runs first. Tests then need hooks in production code that age one timestamp of one subsystem at a time, and such a test never exercises the rest of the world moving on. The server is single-threaded per step, so nothing requires each decision to have its own time.

Decision ​

  • Step time: one Instant per server step: receiving a packet, a timer tick, a lifecycle transition, a deadline, a console command. Only the server loop sets it, at the entry of the step (Session::set_now); it never goes back. Session, Roster, the server facade, routing and Lua timers read it; domain modules receive it as a parameter.
  • It lives in one place, the Roster. Session sets and reads it there; the facade sees it through the Roster.
  • Calendar time follows the step too. The loop anchors a wall-clock reading (unix seconds) at start and on every tick; what an outside party dates on the calendar, such as the expiry of a web sign-in token, is the step time converted through that anchor.
  • Only limits on real execution read the real clock: the server loop (step time, timer schedule, uptime), the time limit of a Lua callback, the time limit of a storage statement, and log throttling. They bound how long code actually runs and how much it logs, not game time.
  • Tests move the step time (Session::advance); there are no hooks that age one subsystem. A test that needs time to pass lets all of it pass: every other timer of the world runs too.

Consequences ​

  • All decisions of one step see the same time, so checks on the edge of a window do not depend on their order.
  • New code in src/ outside main.rs does not call Instant::now(); it takes the step's time.
  • A step that was not opened sees the time of the previous one. That is why only the entry points of the server loop set the time.
  • A few places still read the OS calendar clock directly: ban expiry, the saved_at stamp of a saved map and the realtime mode of the world clock.

Alternatives considered ​

  • Pass now to every method of the roster and the facade. The most explicit option, but time would appear in hundreds of signatures and in every Lua binding, with no gain over a step time: the step is single-threaded anyway.
  • A clock adapter (a trait, or an enum of real and manual clocks). It gives a real seam, but a manual clock is mutable state shared between the test and the session, again with no gain over a step time.

Documentation licensed under CC BY 4.0. DarqlineMP is not affiliated with Rockstar Games or Take-Two Interactive. Grand Theft Auto is a trademark of Take-Two Interactive Software, Inc.