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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user