Add host-authoritative world-state sync

Implement host-authoritative time/weather sync across server and clients. Adds a new worldState packet (and worldStateHost for host assignment) and server-side host tracking/reassignment to the lowest remaining playerId when the host disconnects. Plugin-side changes introduce F4T::WorldStateSync (new header + implementation), polling/apply hooks in the game-thread main loop, and Networking support to send/receive/parse worldState/worldStateHost packets. The server validates host-only sends, relays worldState to peers, broadcasts worldStateHost on host handoff, and augments logging/stats. Protocol and documentation files updated (protocol/world-state.md, docs/*) and server/fake_client.py extended to track/print host world-state snapshots.
This commit is contained in:
2026-06-22 21:50:59 +12:00
parent 9ceb6601ae
commit 99595ef87c
15 changed files with 1042 additions and 33 deletions
+1
View File
@@ -8,6 +8,7 @@ The first protocol should be simple and focused on player transform sync.
- Welcome packet
- Transform packet
- World state packet (host time and weather) — see [`world-state.md`](world-state.md)
- Disconnect packet
## Future Packet Types
+13 -3
View File
@@ -9,6 +9,8 @@ These message types are currently implemented:
```text
welcome
transform
worldState
worldStateHost
disconnect
```
@@ -17,11 +19,16 @@ Current behavior:
- `welcome` is sent by the server after assigning a `playerId`.
- `transform` is sent by the Fallout 4 plugin, processed by the server, and
broadcast to other connected clients.
- `worldState` is sent by the current world-state host, validated by the
server, and broadcast to other connected clients.
- `worldStateHost` is sent in `welcome` and when the host disconnects to
reassign host to the lowest remaining `playerId`.
- `disconnect` is sent by the server when a client disconnects.
The Fallout 4 plugin sends transform packets and receives `welcome`,
`transform`, and `disconnect` packets. Received transforms are stored as plain
remote-player state only.
`transform`, `worldState`, and `disconnect` packets. Received transforms are
stored as plain remote-player state. Non-host clients apply received world state
on the game thread.
## Current Receivers
@@ -30,11 +37,14 @@ The Fallout 4 plugin and `server/fake_client.py` currently receive:
```text
welcome
transform
worldState
worldStateHost
disconnect
```
The plugin and fake client store broadcast transform packets in in-memory remote
player state tables and remove entries when disconnect packets arrive.
player state tables and remove entries when disconnect packets arrive. The fake
client also tracks the latest host world-state snapshot.
## Planned Later
+77
View File
@@ -0,0 +1,77 @@
# 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",
"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 |
| `clientTime` | number (double) | no | Sender Unix epoch seconds |
| `serverTime` | number (double) | no | Relay timestamp added by server |
## Send Cadence (Host)
- About 1 Hz heartbeat while connected
- Immediate send when `weatherFormId` changes
- Immediate send when `gameHour` or `gameDaysPassed` jumps beyond a small threshold (wait, sleep, console time changes)
## Client Apply Rules
- **Time:** non-host clients snap local calendar globals when host values differ beyond threshold.
- **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.
+10 -2
View File
@@ -1,9 +1,17 @@
# World Sync
World synchronization is a long-term problem and is not part of the first prototype.
Fallout 4 assumes a single player owns the world state. Multiplayer requires deciding which client or server controls each piece of world state.
## Implemented (MVP)
Host-authoritative time and weather sync is implemented via the `worldState`
packet. See [`world-state.md`](world-state.md) for the field spec.
- Host: tracked by `worldStateHostPlayerId` (first connected client; reassigned to lowest remaining `playerId` on host disconnect)
- Synced fields: `gameHour`, `gameDaysPassed`, optional `weatherFormId`
- Weather apply: exterior cells only on non-host clients
- Server: pass-through relay with host-only send guard
## World State Examples
World state can include: