Add appearance sync and late-join replay

Adds optional `appearance` snapshots to transform packets, applies supported appearance data to runtime proxies, and has the server replay stored transforms to newly connected clients so late joiners receive current visuals immediately.
This commit is contained in:
2026-06-29 16:46:50 +12:00
parent ad7f287899
commit 4b8f5246c4
15 changed files with 772 additions and 38 deletions
+32
View File
@@ -73,6 +73,17 @@ Example after server processing:
{ "slot": "body", "formId": "0001F66A" },
{ "slot": "headband", "formId": "" }
],
"appearance": {
"version": 1,
"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": "",
"headParts": [],
"morphs": []
},
"serverTime": 1780212128.3011043
}
```
@@ -97,6 +108,7 @@ 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.
```
@@ -143,6 +155,26 @@ empty strings for unequipped tracked slots. Power armor, weapon models,
condition, legendary instance data, tint/material overrides, and forms missing
from the receiver load order are outside this packet extension.
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 1.
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.
headParts Hex-string `HDPT` form IDs for the sender's current head parts.
morphs Morph slider values as `{ "id": hex-string, "value": number }` entries.
```
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.
+17
View File
@@ -18,6 +18,12 @@ local movement logs: while the player is moving, the plugin targets roughly
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.
@@ -64,6 +70,7 @@ weaponDrawn
movementSpeed
animationGraphSpeed
equippedItems
appearance
lastReceivedLocalTime
```
@@ -79,6 +86,13 @@ 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
@@ -115,6 +129,9 @@ player exists.
- Transform packets can include optional `equippedItems` snapshots for visible
clothing, armor, hats, and eyewear slots. The game-thread proxy controller
applies those snapshots after proxy 3D is loaded.
- 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