Files
Commonwealth-Online-Public/protocol/player-sync.md
T
andrew 89c3e82554 Implement proxy weapon animation sync for ranged guns
Milestone 1: Proxy actors now equip remote players' right-hand weapons and play armed idle/locomotion poses when weaponDrawn=true. Adds rightHand weapon slot to equippedItems protocol, weapon graph variable support (iSyncGunDown: 0=drawn, 1=holstered), and weapon FSM events (weaponDraw, readyStateEnter, gunDownStateEnter). Fixes two critical animation state churn bugs: frustum-visibility flicker no longer resets animation state on every update tick, and idle proxies no longer register as changed on every frame. Includes WeaponBehavior graph loading via AIProcess::RequestLoadAnimationsForWeaponChange() and manual weapon attachment fallback. New docs/weapon-animation-sync.md reference guide maps animation events and variables from F4-Animation-Research. Experimental alert-state reassertion for armed proxies to test NPC combat state gating of weapon-ready poses.
2026-07-02 17:23:09 +12:00

181 lines
7.9 KiB
Markdown

# 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