Files
Commonwealth-Online-Public/protocol/packets.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

8.3 KiB

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:

{
  "type": "welcome",
  "playerId": 1,
  "serverTime": 1780212098.457079
}

Fields:

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:

{
  "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:

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:

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:

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:

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:

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:

{
  "type": "disconnect",
  "playerId": 2,
  "serverTime": 1780212134.0
}

Fields:

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:

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.