Introduce an optional timeSync field and event-driven time pushes so host time is only re-applied on connect, resume-from-freeze, or large jumps while weather continues on ~1 Hz heartbeats. Added IsGameTimeFrozen detection and client buffering of latest host time; clients apply host time only when timeSync is true or when gameplay just resumed. Updated networking to read/write timeSync, adjusted F4TWorldStateSync logic (separate time vs weather send/apply paths, changed function signatures and behavior), and updated protocol/docs/changelog and fake_client logging to reflect the new field and behavior.
3.3 KiB
3.3 KiB
World State Packet
Host-authoritative time and weather sync for the Commonwealth Online prototype.
Authority
- The server tracks a
worldStateHostPlayerIdfor the session. - The first connected client becomes host. When the host disconnects, the server
reassigns host to the lowest remaining
playerIdand broadcastsworldStateHost. - Only the current host sends
worldStatepackets. - The Python relay validates the sender and broadcasts to all other clients.
Promoted hosts keep their already-synced local time and weather; reassignment should not change world state for remaining clients.
worldStateHost Packet
Sent in welcome (initial host) and broadcast when the host disconnects:
{
"type": "worldStateHost",
"worldStateHostPlayerId": 2,
"serverTime": 1780212128.301
}
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | Must be "worldStateHost" |
worldStateHostPlayerId |
integer | yes | Connected player ID that is now world-state host |
serverTime |
number (double) | no | Relay timestamp |
The welcome packet also includes optional worldStateHostPlayerId so new joiners
know the current host immediately.
Packet Format
{
"type": "worldState",
"playerId": 1,
"gameHour": 14.25,
"gameDaysPassed": 12.5,
"weatherFormId": "00123456",
"timeSync": true,
"clientTime": 1780212128.301,
"serverTime": 1780212128.301
}
Field Definitions
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | Must be "worldState" |
playerId |
integer | yes (server) | Injected by server; identifies the sending host |
gameHour |
number (float) | yes | In-game hour of day, 0.0–24.0 |
gameDaysPassed |
number (float) | yes | In-game days passed global |
weatherFormId |
string | no | 8-character hex TESWeather form ID; omitted or "" when host is indoors |
timeSync |
boolean | no | When true, non-host clients should apply gameHour and gameDaysPassed. Omitted or false on weather-only heartbeats. |
clientTime |
number (double) | no | Sender Unix epoch seconds |
serverTime |
number (double) | no | Relay timestamp added by server |
Send Cadence (Host)
- About 1 Hz heartbeat for weather while connected (
timeSyncomitted orfalse) - Immediate send with
timeSync: truewhen:- First connected or promoted to host
- Game time resumes after a freeze (menus, pip-boy, dialogue, loading screens, save/load, wait/sleep)
gameHourorgameDaysPassedjumps beyond a small threshold (wait, sleep, console time changes)
- Immediate send when
weatherFormIdchanges (includes current time fields; setstimeSynconly when time also changed or on resume)
Client Apply Rules
- Time: non-host clients track the latest host time from every packet but only snap local calendar globals when
timeSyncistrueand gameplay time is not frozen, or immediately when gameplay time resumes after a freeze (using the latest stored host values). - Weather: non-host clients apply forced weather only when the local player is in an exterior cell.
- Missing optional fields use safe defaults on receive.
Backward Compatibility
Older clients that do not send or parse worldState continue to work. Transform sync is unchanged.