Synchronised time and weather

Synchronised session time and weather: the network model, shared configuration, ACL-gated commands, random weather events, and the server and client APIs.

3 min read685 wordsMarkdown

cyberm_weather is the single authority for a session's time and weather. Every player sees the same clock and the same sky, and neither drifts: the server holds the canonical state and clients project it locally.

At server boot the canonical state is 12:00:00, the weather is sunny, and the clock advances at timeScale. A player joining later receives the current time, never the boot time, and joins a weather transition already in progress at its current point rather than restarting it.

Use this guide if you are writing a resource that needs to read the time, react to weather, or — from a trusted server resource — change either.

Network model

server monotonic clock + canonical state
                 |
                 | versioned snapshot / reliable broadcast
                 v
 client request ---- RTT/2 ----> local REDengine projection
                 ^
                 | periodic resynchronisation

The snapshot carries authorityEpoch, revision, secondsOfDay, rate, frozen, the weather name and preset, the transition, the priority, and the deadline of the next random event. The client measures the round trip of its own request, adds at most two seconds of half-RTT, then re-anchors its monotonic reference. A mutation broadcast applies immediately; a reply carrying an older revision of the same epoch is rejected. A new epoch lets a server hot-reload restart at revision 1 without leaving clients stuck on the previous incarnation.

CyberM projects the server clock twice a second. It deliberately does not hold REDengine's SetPausedState: two-client runtime testing proved that this flag can also slow gameplay and vehicle physics. Periodic absolute correction prevents long-term clock drift without changing the simulation rate. At midnight, circular arithmetic turns the roll over to 00:00 into normal forward motion. A small step back caused by a late packet is ignored, so REDengine's "next occurrence" semantics are not triggered — which would jump a whole day.

Configuration

Edit resources/cyberm_weather/shared/config.lua:

  • startupTime12:00:00 by default;
  • timeScale — game seconds per real second (4.0);
  • syncIntervalMs — full resynchronisation (15000);
  • applyIntervalMs — projection frequency (500);
  • heartbeatIntervalMs — authoritative server broadcast (5000);
  • environmentEnforceIntervalMs — local lock and preset check (5000);
  • randomWeather and initialWeatherDurationSeconds;
  • presets — weight, real min/max duration, and transition for each weather.

The file is shared and therefore public. It must hold no secret, key, or ACL.

Presets supplied: sunny, lightclouds, cloudy, rain, heavyclouds, fog, pollution, and sandstorm. The REDengine 24h_weather_* values are still accepted server-side.

Server commands and ACL

From the in-game CyberM terminal or the dedicated console:

weather.status
weather.time.set 06:45
weather.time.freeze
weather.time.resume
weather.rate 12
weather.set fog 25
weather.random off
weather.next

weather, weather.status, and weather.time are read-only. Every other command uses RegisterCommand(..., true) and is authorised against the player's authenticated public identity. For example:

{
  "permissions": ["command.weather.*"]
}

The client DLL exposes no local time.* or weather.* command, so every interactive change goes through the server and its ACL. The network events the resource accepts serve only to request a snapshot: no client mutation event exists.

API for a server resource

These events are local to the server runtime:

TriggerEvent("cyberm:weather:setTime", 20, 15, 0)
TriggerEvent("cyberm:weather:setRate", 4)
TriggerEvent("cyberm:weather:setFrozen", false)
TriggerEvent("cyberm:weather:setWeather", "rain", 30)
TriggerEvent("cyberm:weather:setRandomEnabled", true)

To observe the state:

AddEventHandler("cyberm:weather:state", function(state)
    print(("weather=%s revision=%d"):format(state.weather, state.revision))
end)

TriggerEvent("cyberm:weather:requestState")

cyberm:weather:timeChanged and cyberm:weather:weatherChanged report the precise cause. These APIs are meant for trusted server resources; server scripts are not distributed to players.

API for a client resource

Listening, with no dependency:

AddEventHandler("cyberm:weather:updated", function(state)
    print(state.weather, state.rate, state.frozen)
end)

A one-off read through an export:

CreateThread(function()
    local promise, reason = CyberM.exports.call("cyberm_weather", "getState")
    assert(promise, reason)
    local state = promise:await()
    print(string.format("%02d:%02d:%02d", state.hour, state.minute, state.second))
end)

Available exports:

  • isReady() — has the first snapshot arrived?
  • getState() — time predicted at the moment of the call, and the weather state;
  • requestSync() — forces a reliable resynchronisation request.

The native CyberM.environment table (getTime, setTime, setTimeFrozen, setWeather, setWeatherFrozen, isWeatherFrozen) is guarded by the world.environment permission. It exists to implement the authority, not for ordinary gameplay scripts.

Random weather events

The server makes a weighted draw that excludes the current weather, applies the new preset's transition, then schedules its real min/max duration. The seed comes from the process's monotonic clock at the first tick, so two boots do not systematically replay the same sequence. weather.random off suspends the draws without changing the current weather; weather.random on cleanly reschedules the deadline.

Deployment

The resource sits under the configured resources.root. The server watcher prepares its VM, rebuilds the signed set, and distributes only the manifest, the client and shared scripts, and this README. The new client DLL is required for the CyberM.environment primitive; if it is not loaded yet, the resource stays inert and explicitly asks for Cyberpunk to be restarted.

For agents and LLMs: this page as Markdown · llms.txt · llms-full.txt