Repo Cleanup
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# Protocol
|
||||
|
||||
This folder documents the planned network protocol for Commonwealth Online.
|
||||
|
||||
The first protocol should be simple and focused on player transform sync.
|
||||
|
||||
## First Packet Types
|
||||
|
||||
- Welcome packet
|
||||
- Transform packet
|
||||
- World state packet (host time and weather) — see [`world-state.md`](world-state.md)
|
||||
- Server world state packet (dev GUI weather/time) — see [`server-world-state.md`](server-world-state.md)
|
||||
- Disconnect packet
|
||||
|
||||
## Future Packet Types
|
||||
|
||||
- Animation state packet
|
||||
- Fire weapon packet
|
||||
- Health packet
|
||||
- Chat packet
|
||||
- Cell change packet
|
||||
|
||||
## Design Goals
|
||||
|
||||
The protocol should be:
|
||||
|
||||
- Simple
|
||||
- Easy to debug
|
||||
- Versioned
|
||||
- Safe to extend
|
||||
- Clear enough to test outside Fallout 4
|
||||
|
||||
## Early Testing
|
||||
|
||||
The protocol should be testable using fake clients before being connected to Fallout 4.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Message Types
|
||||
|
||||
This file tracks current and planned message type names for the Commonwealth Online local networking prototype.
|
||||
|
||||
## Implemented
|
||||
|
||||
These message types are currently implemented:
|
||||
|
||||
```text
|
||||
welcome
|
||||
transform
|
||||
worldState
|
||||
worldStateHost
|
||||
serverWorldState
|
||||
disconnect
|
||||
```
|
||||
|
||||
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`.
|
||||
- `serverWorldState` is sent by the server to all connected clients when the
|
||||
dev server GUI sets weather or time.
|
||||
- `disconnect` is sent by the server when a client disconnects.
|
||||
|
||||
The Fallout 4 plugin sends transform packets and receives `welcome`,
|
||||
`transform`, `worldState`, `serverWorldState`, 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
|
||||
|
||||
The Fallout 4 plugin and `server/fake_client.py` currently receive:
|
||||
|
||||
```text
|
||||
welcome
|
||||
transform
|
||||
worldState
|
||||
worldStateHost
|
||||
serverWorldState
|
||||
disconnect
|
||||
```
|
||||
|
||||
The plugin and fake client store broadcast transform packets in in-memory remote
|
||||
player state tables and remove entries when disconnect packets arrive. The fake
|
||||
client also tracks the latest host world-state snapshot.
|
||||
|
||||
## Planned Later
|
||||
|
||||
These message types are planned but not implemented yet:
|
||||
|
||||
```text
|
||||
ping
|
||||
pong
|
||||
chat_message
|
||||
animation_state
|
||||
weapon_fire
|
||||
health_update
|
||||
cell_change
|
||||
actor_spawn
|
||||
actor_remove
|
||||
```
|
||||
|
||||
`cell_change` is currently represented through the `movementType` field inside
|
||||
`transform` packets. It is not its own packet type yet.
|
||||
|
||||
Remote actor spawning, combat, inventory, quest, settlement, and broader
|
||||
gameplay sync are planned later and are outside the current local prototype.
|
||||
|
||||
## Naming Rules
|
||||
|
||||
Message types should be:
|
||||
|
||||
- Lowercase
|
||||
- Snake case
|
||||
- Short but clear
|
||||
- Stable once used in test builds
|
||||
@@ -0,0 +1,250 @@
|
||||
# Packet Types
|
||||
|
||||
This document describes the currently implemented local prototype packet types
|
||||
for Commonwealth Online.
|
||||
|
||||
The current protocol is newline-separated JSON over a local TCP connection to
|
||||
`127.0.0.1:7777`.
|
||||
|
||||
Implemented packet types:
|
||||
|
||||
- `welcome`
|
||||
- `transform`
|
||||
- `disconnect`
|
||||
|
||||
The current system is still a local networking prototype. It is not playable
|
||||
multiplayer yet.
|
||||
|
||||
## Implemented
|
||||
|
||||
### Welcome Packet
|
||||
|
||||
Sent from the server to a newly connected client after the server assigns a
|
||||
`playerId`.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "welcome",
|
||||
"playerId": 1,
|
||||
"serverTime": 1780212098.457079
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
```text
|
||||
type Packet type. Always "welcome".
|
||||
playerId Server-assigned player ID for this connection.
|
||||
serverTime Server timestamp when the welcome packet was created.
|
||||
```
|
||||
|
||||
The Fallout 4 plugin and `server/fake_client.py` receive this packet. The plugin
|
||||
stores the assigned `playerId` for filtering its own future transform echoes.
|
||||
|
||||
### Transform Packet
|
||||
|
||||
Sent by the Fallout 4 plugin to the local server when the local player's
|
||||
transform changes. The server adds `playerId` and `serverTime`, then broadcasts
|
||||
the processed packet to other connected clients.
|
||||
|
||||
Example after server processing:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "transform",
|
||||
"playerId": 2,
|
||||
"x": -81781.26,
|
||||
"y": 87962.61,
|
||||
"z": 7696.05,
|
||||
"angleZ": 3.20,
|
||||
"movementType": "normal",
|
||||
"cellId": "0000DD60",
|
||||
"worldspaceId": "0000003C",
|
||||
"clientTime": 1780212128.301,
|
||||
"isMoving": true,
|
||||
"isSprinting": false,
|
||||
"isSneaking": false,
|
||||
"isJumping": false,
|
||||
"weaponDrawn": false,
|
||||
"movementSpeed": 186.4,
|
||||
"equippedItems": [
|
||||
{ "slot": "body", "formId": "0001F66A" },
|
||||
{ "slot": "headband", "formId": "" },
|
||||
{ "slot": "rightHand", "formId": "0001F4A6" }
|
||||
],
|
||||
"appearance": {
|
||||
"version": 3,
|
||||
"raceFormId": "00013746",
|
||||
"height": 1.0,
|
||||
"morphWeight": { "thin": 0.0, "muscular": 0.0, "large": 0.0 },
|
||||
"bodyTintColor": { "r": 255, "g": 220, "b": 190, "a": 255 },
|
||||
"hairColorFormId": "",
|
||||
"facialHairColorFormId": "",
|
||||
"complexionFormId": "",
|
||||
"headParts": [],
|
||||
"morphs": [],
|
||||
"morphRegions": [],
|
||||
"facialBoneMorphs": [],
|
||||
"tints": []
|
||||
},
|
||||
"serverTime": 1780212128.3011043
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
```text
|
||||
type Packet type. Always "transform".
|
||||
playerId Server-assigned player ID for the sender.
|
||||
x Local player X position.
|
||||
y Local player Y position.
|
||||
z Local player Z position.
|
||||
angleZ Local player Z rotation angle.
|
||||
movementType Movement classification for this transform update.
|
||||
cellId Current cell form ID as a string.
|
||||
worldspaceId Current worldspace form ID as a string.
|
||||
clientTime Timestamp generated by the sending plugin.
|
||||
isMoving Optional movement-state flag; defaults to false when missing.
|
||||
isSprinting Optional movement-state flag; defaults to false when missing.
|
||||
isSneaking Optional movement-state flag; defaults to false when missing.
|
||||
isJumping Optional movement-state flag; defaults to false when missing.
|
||||
weaponDrawn Optional weapon drawn state; defaults to false when missing.
|
||||
movementSpeed Optional derived movement speed in game units per second; defaults to 0.0 when missing.
|
||||
equippedItems Optional full snapshot of tracked visible apparel slots. Each entry has a string `slot` and hex-string `formId`; an empty `formId` means the slot is intentionally unequipped.
|
||||
appearance Optional versioned player appearance snapshot for runtime proxy visuals.
|
||||
serverTime Timestamp added by the server before broadcast.
|
||||
```
|
||||
|
||||
Possible `movementType` values:
|
||||
|
||||
```text
|
||||
normal
|
||||
cell_change
|
||||
worldspace_change
|
||||
teleport
|
||||
```
|
||||
|
||||
`cell_change` and `worldspace_change` are currently represented through
|
||||
`movementType` inside transform packets. They are not separate packet types yet.
|
||||
|
||||
Movement state fields are data-only for now. Receivers must treat them as
|
||||
optional and must not reject older transform packets when they are absent.
|
||||
|
||||
Equipment fields are optional and additive. When `equippedItems` is missing,
|
||||
receivers keep their existing/default proxy appearance. When present, it is a
|
||||
complete snapshot for the tracked visible apparel slots:
|
||||
|
||||
```text
|
||||
hairTop
|
||||
hairLong
|
||||
body
|
||||
underTorso
|
||||
underLeftArm
|
||||
underRightArm
|
||||
underLeftLeg
|
||||
underRightLeg
|
||||
aboveTorso
|
||||
aboveLeftArm
|
||||
aboveRightArm
|
||||
aboveLeftLeg
|
||||
aboveRightLeg
|
||||
headband
|
||||
eyes
|
||||
scalp
|
||||
```
|
||||
|
||||
Tracked slots include 16 visible apparel BIPED slots (listed above) plus:
|
||||
|
||||
```text
|
||||
rightHand Right-hand weapon slot (WEAP form ID); optional
|
||||
```
|
||||
|
||||
The equipment-sync milestones send resolved `ARMO` and `WEAP` form IDs and
|
||||
empty strings for unequipped tracked slots. Off-hand weapons, power armor,
|
||||
weapon condition, legendary instance data, tint/material overrides, and forms
|
||||
missing from the receiver load order are outside this packet extension.
|
||||
|
||||
When `rightHand` is missing, receivers keep their existing proxy weapon. When
|
||||
present with an empty `formId`, the proxy is unequipped. When a `WEAP` form ID
|
||||
is not found locally, receivers log a throttled warning and skip equipping
|
||||
that weapon; the proxy continues with its current state and other sync intact.
|
||||
|
||||
Appearance fields are optional and additive. When `appearance` is missing,
|
||||
receivers keep the proxy's existing/default body and face. When present, it is a
|
||||
best-effort snapshot for male-proxy-compatible runtime visuals:
|
||||
|
||||
```text
|
||||
version Appearance payload version. Current value is 3.
|
||||
raceFormId Hex-string race form ID captured for diagnostics/future use; receivers do not switch proxy race yet.
|
||||
height NPC height scalar.
|
||||
morphWeight Body triangle weights: thin, muscular, and large.
|
||||
bodyTintColor Body tint channels r, g, b, a as 0-255 integers.
|
||||
hairColorFormId Hex-string `CLFM` form ID for hair color, or empty when unavailable.
|
||||
facialHairColorFormId Hex-string `CLFM` form ID for facial hair color, or empty when unavailable.
|
||||
complexionFormId Hex-string `TXST` form ID for skin complexion (hands, arms, body tone), or empty when unavailable.
|
||||
headParts Hex-string `HDPT` form IDs for the sender's current head parts.
|
||||
morphs Morph slider values as `{ "id": hex-string, "value": number }` entries.
|
||||
morphRegions Region morph slider values as a flat array of numbers (added in version 2).
|
||||
facialBoneMorphs Bone-based facial morphs as `{ "id": hex-string, "position": [x,y,z], "rotation": [x,y,z], "scale": [x,y,z] }` entries (added in version 2).
|
||||
tints Character tint layers (skin tone, complexion, makeup, beard shade) as `{ "id": number, "type": number, "value": number, "color": hex-string, "swatch": number }` entries (added in version 3). `id` is the race tint-template uniqueID, `type` is the tint entry type (0 mask, 1 palette, 2 texture), and `value` is the 0-255 intensity. `color` (ARGB hex) and `swatch` are present only for palette entries.
|
||||
```
|
||||
|
||||
`morphRegions` and `facialBoneMorphs` are optional and additive. Version 1
|
||||
senders omit them; receivers must treat their absence as "no change" and keep
|
||||
existing proxy morph data.
|
||||
|
||||
`tints` is optional and additive (added in version 3). Earlier senders omit it;
|
||||
receivers must treat its absence as "no change". Receivers apply a tint by
|
||||
matching `id` against an existing proxy tint entry (updating intensity/color in
|
||||
place) and only reconstruct a missing entry when the proxy race exposes a
|
||||
matching tint template, skipping unresolved tints rather than failing.
|
||||
|
||||
Receivers must treat missing or malformed appearance subfields as non-fatal and
|
||||
apply only the parts they can resolve locally. Gender is intentionally not part
|
||||
of this payload yet; female proxy support can add it later.
|
||||
|
||||
### Disconnect Packet
|
||||
|
||||
Sent by the server to remaining connected clients when a client disconnects.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "disconnect",
|
||||
"playerId": 2,
|
||||
"serverTime": 1780212134.0
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
```text
|
||||
type Packet type. Always "disconnect".
|
||||
playerId Server-assigned player ID that disconnected.
|
||||
serverTime Server timestamp when the disconnect packet was created.
|
||||
```
|
||||
|
||||
The Fallout 4 plugin and `server/fake_client.py` use this packet to remove
|
||||
remote players from their in-memory state tables.
|
||||
|
||||
## Receiver Behavior
|
||||
|
||||
The Fallout 4 plugin and fake client both receive:
|
||||
|
||||
```text
|
||||
welcome
|
||||
transform
|
||||
disconnect
|
||||
```
|
||||
|
||||
The plugin receive loop runs on a background networking thread. It stores plain
|
||||
remote-player state only and does not spawn actors or touch Fallout 4 game
|
||||
objects.
|
||||
|
||||
## Planned Later
|
||||
|
||||
- Remote actor spawning.
|
||||
- Gameplay synchronization.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Player Sync
|
||||
|
||||
Player synchronization is the first major technical goal for Commonwealth Online.
|
||||
The current implementation is still a local networking prototype.
|
||||
|
||||
## Current Implemented Flow
|
||||
|
||||
```text
|
||||
Fallout 4 Plugin ↔ Local Python Server ↔ Other Clients
|
||||
↓
|
||||
Plugin Remote Player State
|
||||
```
|
||||
|
||||
The Fallout 4 plugin currently reads the local player's transform and sends it
|
||||
to the local Python server. Normal movement sends are throttled separately from
|
||||
local movement logs: while the player is moving, the plugin targets roughly
|
||||
10 Hz and skips sends until position or rotation changes meaningfully. The
|
||||
server assigns a `playerId`, adds `serverTime`, and broadcasts transform packets
|
||||
to other connected clients.
|
||||
|
||||
When a new client connects, the server sends the last stored transform snapshot
|
||||
for each already-connected player to that new client after `welcome`. These
|
||||
snapshots include optional fields such as `equippedItems` and `appearance`, so a
|
||||
late joiner can initialize proxy visuals before the existing players' next idle
|
||||
heartbeat or movement packet.
|
||||
|
||||
The Fallout 4 plugin also receives server packets on a background networking
|
||||
thread. It stores its assigned `playerId`, stores remote transform state by
|
||||
remote `playerId`, and removes remote state when disconnect packets arrive.
|
||||
`server/fake_client.py` remains a lightweight test receiver for the same packet
|
||||
lifecycle.
|
||||
|
||||
The plugin's game-thread proxy actor controller reads a copied snapshot of this
|
||||
remote-player state and dynamically spawns/moves runtime proxy actors in the
|
||||
local player's current loaded cell. The networking thread still does not touch
|
||||
Fallout 4 actors or references. If a remote player is not in the same
|
||||
cell/worldspace, the controller holds that player's proxy at a hidden in-cell
|
||||
position instead of leaving it at the last visible location.
|
||||
|
||||
`COVault109` is treated as a solo cell for the first-time character-creation
|
||||
flow. On entry the plugin can send one location update so other clients hold any
|
||||
stale proxy, then it suppresses outgoing transform packets while the local
|
||||
player remains inside and the proxy controller skips remote representation. This
|
||||
is a client-side visibility/isolation rule and does not change the transform
|
||||
packet schema.
|
||||
|
||||
## Current Remote Player State Model
|
||||
|
||||
The plugin and fake client store remote players by `playerId`.
|
||||
|
||||
Each remote player state entry tracks:
|
||||
|
||||
```text
|
||||
playerId
|
||||
x
|
||||
y
|
||||
z
|
||||
angleZ
|
||||
movementType
|
||||
cellId
|
||||
worldspaceId
|
||||
clientTime
|
||||
serverTime
|
||||
isMoving
|
||||
isSprinting
|
||||
isSneaking
|
||||
isJumping
|
||||
isCrouching
|
||||
weaponDrawn
|
||||
movementSpeed
|
||||
animationGraphSpeed
|
||||
equippedItems
|
||||
appearance
|
||||
lastReceivedLocalTime
|
||||
```
|
||||
|
||||
Required transform packet fields are `type`, `playerId`, `x`, `y`, `z`,
|
||||
`angleZ`, and `cellId`. `worldspaceId`, `movementType`, `clientTime`, and
|
||||
`serverTime` are optional; interior test cells may omit `worldspaceId`.
|
||||
Movement state fields are also optional and default to not moving, not
|
||||
sprinting, not sneaking, not jumping, weapon holstered, and speed `0.0` when
|
||||
missing.
|
||||
|
||||
`equippedItems` is also optional. New clients send it as a complete snapshot of
|
||||
tracked visible apparel slots with `{ slot, formId }` entries; empty `formId`
|
||||
values mean that tracked slot is intentionally unequipped. Older clients can
|
||||
omit the field and receivers keep the proxy's existing/default appearance.
|
||||
|
||||
`appearance` is optional and versioned. It carries male-proxy-compatible body
|
||||
and face data such as race form ID for diagnostics/future use, height, body
|
||||
morph weight, body tint color, hair color, facial hair color, head-part form IDs,
|
||||
and morph slider values. Receivers apply supported fields only after proxy 3D is
|
||||
loaded. Gender switching is intentionally omitted until female proxy support is
|
||||
available.
|
||||
|
||||
This model proves the data shape and lifecycle before spawning remote actors.
|
||||
For the current visual milestone, the controller represents only one remote
|
||||
player and chooses the lowest available `playerId` if more than one remote
|
||||
player exists.
|
||||
|
||||
## Implemented
|
||||
|
||||
- The plugin sends local player transform packets.
|
||||
- Normal transform sends are independent from movement logging and are
|
||||
thresholded to avoid packet-per-frame traffic.
|
||||
- The plugin receives `welcome` packets and stores its assigned `playerId`.
|
||||
- The plugin receives broadcast transform packets.
|
||||
- The plugin ignores transform packets for its own assigned `playerId`.
|
||||
- The plugin stores remote player state by `playerId`.
|
||||
- The plugin receives `disconnect` packets and removes remote player state.
|
||||
- The server assigns incrementing `playerId` values.
|
||||
- The server sends `welcome` packets.
|
||||
- The server adds `playerId` and `serverTime` to transform packets.
|
||||
- The server broadcasts transform packets to other connected clients.
|
||||
- The fake client stores remote player state by `playerId`.
|
||||
- The server broadcasts `disconnect` packets.
|
||||
- The fake client removes disconnected remote players from its state table.
|
||||
- The plugin moves the single placed test proxy actor from a thread-safe
|
||||
remote-player snapshot on the game-thread update path.
|
||||
- Normal proxy movement is smoothed toward the latest received target, while
|
||||
`cell_change`, `worldspace_change`, and `teleport` transforms snap directly to
|
||||
avoid slow movement across large discontinuities.
|
||||
- The game-thread proxy controller holds the single proxy at a hidden in-cell
|
||||
position when the represented player disconnects, disappears, or leaves the
|
||||
local test cell, then snaps back to a valid same-cell remote player before
|
||||
resuming smoothing.
|
||||
- Transform packets include movement state data (`isMoving`, `movementSpeed`,
|
||||
`isSprinting`, `isSneaking`, `isJumping`, `weaponDrawn`).
|
||||
- Transform packets can include optional `equippedItems` snapshots for visible
|
||||
clothing, armor, hats, eyewear, and right-hand weapon slots. The game-thread
|
||||
proxy controller applies those snapshots after proxy 3D is loaded. Weapons are
|
||||
synced as optional `WEAP` form IDs; missing weapons or unresolved form IDs do
|
||||
not block other equipment sync.
|
||||
- Transform packets can include optional `appearance` snapshots for best-effort
|
||||
body and face proxy visuals. The game-thread proxy controller applies supported
|
||||
fields after proxy 3D is loaded and defers race/gender switching.
|
||||
- The plugin suppresses transform sends and proxy representation in the
|
||||
`COVault109` solo cell.
|
||||
- The game-thread proxy controller applies confirmed Havok animation graph
|
||||
variables to runtime proxy actors from that remote state (locomotion, sneak,
|
||||
jump). Weapon-drawn state is parsed but animation application is implemented
|
||||
separately (Phase 2 milestone). Transform position/heading sync is unchanged.
|
||||
|
||||
## Transform Cadence
|
||||
|
||||
Normal local movement currently uses three separate rates:
|
||||
|
||||
- Network sends target roughly 10 Hz while the player is moving.
|
||||
- Sends are skipped until position changes by about 3 game units or rotation
|
||||
changes by about 0.02 radians.
|
||||
- Local movement logs stay much slower, around once per second, so
|
||||
`CommonwealthOnline.log` remains readable.
|
||||
|
||||
`cell_change`, `worldspace_change`, and `teleport` movement types bypass the
|
||||
normal send interval and are sent immediately. The receiving plugin still stores
|
||||
remote transform state on the networking thread and moves actors only from the
|
||||
game-thread proxy controller.
|
||||
|
||||
Runtime proxy actors are dynamically spawned per remote `playerId` in the local
|
||||
player's current loaded cell (see architecture). Remote players in a different
|
||||
cell are held at a hidden in-cell position until the local player enters their
|
||||
cell. Full animation graph replication is not implemented; only curated graph
|
||||
variable writes drive proxy locomotion visuals.
|
||||
|
||||
## Planned Later
|
||||
|
||||
- Increase concurrent proxy cap beyond four players per client
|
||||
- Cross-cell actor transfer without respawn (if respawn proves insufficient)
|
||||
- Power armor and weapon model sync
|
||||
|
||||
## Not Required Yet
|
||||
|
||||
- Full animation graph sync
|
||||
- Combat sync
|
||||
- Full inventory sync beyond visible equipped apparel
|
||||
- Quest sync
|
||||
- Dialogue sync
|
||||
- Settlement sync
|
||||
- Accurate physics sync
|
||||
- Public networking or matchmaking
|
||||
@@ -0,0 +1,66 @@
|
||||
# Server World State Packet
|
||||
|
||||
Server-authoritative weather and time control from the dev server GUI.
|
||||
|
||||
## Authority
|
||||
|
||||
- **Time** (`timeHHmm`): sent to **all** clients; each converts `HHmm` to Fallout game-hour float (for example `2200 -> 22.0`, `1830 -> 18.5`) before applying.
|
||||
- **Weather** (`weatherConsoleArg`): sent to **all** clients, but only the current **world-state host** runs `fw <8-digit-id>`. The host then relays usual `worldState` updates to other clients.
|
||||
|
||||
## Packet Format
|
||||
|
||||
Time-only (all clients):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "serverWorldState",
|
||||
"timeHHmm": "1900",
|
||||
"serverTime": 1780212128.301
|
||||
}
|
||||
```
|
||||
|
||||
Weather (all clients receive; only world-state host acts):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "serverWorldState",
|
||||
"weatherConsoleArg": "001ca7e4",
|
||||
"weatherFormId": "001CA7E4",
|
||||
"serverTime": 1780212128.301
|
||||
}
|
||||
```
|
||||
|
||||
## Field Definitions
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `type` | string | yes | Must be `"serverWorldState"` |
|
||||
| `timeHHmm` | string | no | 24-hour clock value (HHmm) converted client-side to game-hour float for `set gamehour to <float>` |
|
||||
| `weatherConsoleArg` | string | no | Exact argument for `fw` on the current world-state host (8-digit lowercase hex) |
|
||||
| `weatherFormId` | string | no | Relay form ID used in host `worldState` broadcast |
|
||||
| `serverTime` | number (double) | no | Relay timestamp |
|
||||
|
||||
## Client Apply Rules
|
||||
|
||||
- **Time:** all clients convert `timeHHmm` to game-hour float and apply `set gamehour to <float>` on the game thread.
|
||||
- **Weather:** only the current world-state host runs `fw <weatherConsoleArg>`.
|
||||
- Weather commands are scheduled on the F4SE game thread from the network receive path.
|
||||
- After the host runs `fw`, it immediately relays `worldState` with `weatherFormId`.
|
||||
|
||||
Edit `server/world_state_presets.py` so each preset uses the exact `fw` ID that works in your console (for example `0000015e`).
|
||||
|
||||
## Preset Weather Console Args
|
||||
|
||||
| Label | Default `fw` arg |
|
||||
|-------|------------------|
|
||||
| Clear | `0002b52a` |
|
||||
| Cloudy | `001cc186` |
|
||||
| Overcast | `001c8556` |
|
||||
| Fog | `001c3473` |
|
||||
| Rain | `001ca7e4` |
|
||||
| Radstorm | `001c3d5e` |
|
||||
| Glowing Sea | `000f1033` |
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
Older clients that do not handle `serverWorldState` ignore the packet safely.
|
||||
@@ -0,0 +1,82 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,50 @@
|
||||
# World Sync
|
||||
|
||||
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:
|
||||
|
||||
- Doors
|
||||
- Containers
|
||||
- Pickups
|
||||
- NPC deaths
|
||||
- Physics objects
|
||||
- Quest stages
|
||||
- Workshop objects
|
||||
- Terminal states
|
||||
- Dialogue state
|
||||
|
||||
## Early Approach
|
||||
|
||||
The first prototype should avoid world sync as much as possible.
|
||||
|
||||
Recommended early restrictions:
|
||||
|
||||
- Use a custom test cell
|
||||
- Avoid lootable containers
|
||||
- Avoid quests
|
||||
- Avoid doors
|
||||
- Avoid terminals
|
||||
- Avoid persistent world changes
|
||||
|
||||
## Later Ideas
|
||||
|
||||
Possible future approaches:
|
||||
|
||||
- Host-controlled world state
|
||||
- Server-authoritative world state
|
||||
- Cell-specific state tracking
|
||||
- Limited interaction whitelist
|
||||
- Resettable test environments
|
||||
Reference in New Issue
Block a user