# World State Packet Host-authoritative time and weather sync for the Commonwealth Online prototype. ## Authority - The server tracks a `worldStateHostPlayerId` for the session. - The first connected client becomes host. When the host disconnects, the server reassigns host to the lowest remaining `playerId` and broadcasts `worldStateHost`. - Only the current host sends `worldState` packets. - 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: ```json { "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 ```json { "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 (`timeSync` omitted or `false`) - Immediate send with `timeSync: true` when: - First connected or promoted to host - Game time resumes after a freeze (menus, pip-boy, dialogue, loading screens, save/load, wait/sleep) - `gameHour` or `gameDaysPassed` jumps beyond a small threshold (wait, sleep, console time changes) - Immediate send when `weatherFormId` changes (includes current time fields; sets `timeSync` only 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 `timeSync` is `true` and 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.