United Offensive protocol 22 wire-schema appendix ================================================= .. meta:: :description: Exact Call of Duty: United Offensive protocol 22 fixed-Huffman seed and ordered entity, vehicle, client, player-state, objective, and HUD delta descriptor tables. :keywords: call of duty, united offensive, protocol 22, netfields, huffman, entitystate, playerstate, snapshot delta Purpose ------- This appendix contains the ordered constants required to implement the compressed message and snapshot codecs described in :doc:`network_protocol`. Descriptor order is part of the wire protocol: it controls ``lastChanged`` interpretation and cannot be rearranged to match a native structure declaration. The ``Bits`` column means: * ``0``: compact-or-full IEEE-754 float mode; * ``-100``: zero-or-16-bit-angle mode; * any other value: the physical width is ``abs(Bits)``; * negative widths other than ``-100`` do not add a separate sign bit. All table indexes are zero-based. Adaptive-Huffman tree update ---------------------------- The compressed ``connect`` request uses the following dynamic tree. These rules are included because using a different adaptive-Huffman variant produces an incompatible connect packet even when NYT handling and bit order look the same. Each node has ``left``, ``right``, ``parent``, ``next``, ``prev``, ``head``, ``weight``, and ``symbol``. ``next``/``prev`` form the weight-ordered node list. Nodes of one weight share a ``head`` slot pointing to that block's leader. The initial tree is one NYT node of weight zero; it is the tree root, list head, list tail, and ``loc[NYT]``. An existing symbol emits its root-to-leaf path (left zero, right one), then increments its leaf. A new symbol emits the NYT path, emits literal value bits 7 through 0, then adds a reference for that symbol. Decoder and encoder perform the same add-reference step after each decoded/encoded byte. Adding a new symbol performs these operations in order: 1. Allocate a symbol leaf and then an internal node, both with weight one. 2. Insert the internal node immediately after NYT in the list, then insert the new leaf immediately after NYT. Join or allocate the corresponding weight-1 leader slots exactly as implied by the neighboring node. 3. Replace NYT at its former parent (or at the root) with the new internal node. 4. Set ``internal.left = NYT`` and ``internal.right = newLeaf``; repair all three parent pointers and publish ``loc[symbol] = newLeaf``. 5. Increment the new internal node's parent. The two new weight-1 nodes are not separately incremented. Incrementing a node is recursive: .. code-block:: text increment(node): if node is null: return if node.next exists and node.next.weight == node.weight: leader = *node.head if leader != node.parent: swap their positions in the binary tree swap leader and node in the linked list if node.prev exists and node.prev.weight == node.weight: *node.head = node.prev else: release node.head leader slot node.weight += 1 if node.next exists and node.next.weight == node.weight: node.head = node.next.head else: node.head = allocate_leader_slot() *node.head = node if node.parent exists: increment(node.parent) if node.prev == node.parent: swap node and node.parent in the linked list if *node.head == node: *node.head = node.parent Tree-position swaps exchange parent/child references without changing list positions. List swaps exchange ``next``/``prev`` positions without changing tree parents. Leader slots are recycled through a free list. This operation order, especially the parent exception and the post-recursion list swap, is protocol-significant. Fixed-Huffman seed ------------------ The sequenced-message Huffman tree is initialized by adding byte symbol 0 ``seed[0]`` times, byte symbol 1 ``seed[1]`` times, and so on through symbol 255. Encoder and decoder build separate but identical trees. They do not update these trees while transforming a message. The values are shown as 16 rows of 16 symbols. The row label is the high hexadecimal nibble of the symbol; columns run from low nibble 0 through F. .. code-block:: text 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 250315 41193 6292 7106 3730 3750 6110 23283 33317 6950 7838 9714 9257 17259 3949 1778 1 8288 1604 1590 1663 1100 1213 1238 1134 1749 1059 1246 1149 1273 4486 2805 3472 2 21819 1159 1670 1066 1043 1012 1053 1070 1726 888 1180 850 960 780 1752 3296 3 10630 4514 5881 2685 4650 3837 2093 1867 2584 1949 1972 940 1134 1788 1670 1206 4 5719 6128 7222 6654 3710 3795 1492 1524 2215 1140 1355 971 2180 1248 1328 1195 5 1770 1078 1264 1266 1168 965 1155 1186 1347 1228 1529 1600 2617 2048 2546 3275 6 2410 3585 2504 2800 2675 6146 3663 2840 14253 3164 2221 1687 3208 2739 3512 4796 7 4091 3515 5288 4016 7937 6031 5360 3924 4892 3743 4566 4807 5852 6400 6225 8291 8 23243 7838 7073 8935 5437 4483 3641 5256 5312 5328 5370 3492 2458 1694 1821 2121 9 1916 1149 1516 1367 1236 1029 1258 1104 1245 1006 1149 1025 1241 952 1287 997 A 1713 1009 1187 879 1099 929 1078 951 1656 930 1153 1030 1262 1062 1214 1060 B 1621 930 1106 912 1034 892 1158 990 1175 850 1121 903 1087 920 1144 1056 C 3462 2240 4397 12136 7758 1345 1307 3278 1950 886 1023 1112 1077 1042 1061 1071 D 1484 1001 1096 915 1052 995 1070 876 1111 851 1059 805 1112 923 1103 817 E 1899 1872 976 841 1127 956 1159 950 7791 954 1289 933 1127 3207 1020 927 F 1355 768 1040 745 952 805 1073 740 1013 805 1008 796 996 1057 11457 13504 Entity-state descriptor tables ------------------------------ Every entity record has the same 60-descriptor extent. A one-bit selector immediately after the 10-bit entity number chooses the vehicle table only when the transmitted entity type is ``ET_VEHICLE`` (numeric value 12). The tables differ in both order and field modes. .. code-block:: text Idx Normal entity field Bits Vehicle entity field Bits 0 pos.trTime 32 pos.trBase[2] 0 1 pos.trBase[0] 0 pos.trBase[0] 0 2 pos.trBase[1] 0 pos.trBase[1] 0 3 pos.trDelta[1] 0 apos.trBase[2] -100 4 pos.trDelta[0] 0 apos.trBase[1] -100 5 angles2[1] 0 apos.trBase[0] -100 6 apos.trBase[1] -100 angles2[1] -100 7 apos.trBase[0] -100 time -16 8 pos.trBase[2] 0 time2 6 9 pos.trDelta[2] 0 origin2[0] 8 10 eventSequence 8 origin2[2] 8 11 eType 8 angles2[0] -100 12 eFlags 24 origin2[1] -100 13 groundEntityNum 10 angles2[2] -100 14 legsAnim 10 leanf -100 15 clientNum 8 loopSound 8 16 apos.trBase[2] -100 eType 8 17 events[0] 8 eFlags 24 18 index 9 eventSequence 8 19 events[1] 8 events[0] 8 20 events[2] 8 events[1] 8 21 events[3] 8 events[2] 8 22 eventParm 8 events[3] 8 23 torsoAnim 10 eventParm 8 24 surfType 8 eventParms[0] 8 25 scale 8 eventParms[1] 8 26 otherEntityNum 10 eventParms[2] 8 27 fWaistPitch 0 eventParms[3] 8 28 pos.trType 8 iHeadIcon 6 29 xmodel 9 iHeadIconTeam 2 30 angles2[0] 0 animMovetype 8 31 fTorsoHeight 0 index 9 32 fTorsoPitch 0 torsoAnim 10 33 apos.trType 8 surfType 8 34 solid 24 scale 8 35 weapon 7 otherEntityNum 10 36 apos.trTime 32 fWaistPitch 0 37 apos.trDelta[0] 0 pos.trType 8 38 eventParms[0] 8 xmodel 9 39 pos.trDuration 32 fTorsoHeight 0 40 animMovetype 4 fTorsoPitch 0 41 eventParms[1] 8 apos.trType 8 42 apos.trDelta[2] 0 solid 24 43 eventParms[2] 8 weapon 7 44 eventParms[3] 8 apos.trTime 32 45 leanf 0 apos.trDelta[0] 0 46 apos.trDelta[1] 0 apos.trDelta[2] 0 47 loopSound 8 apos.trDelta[1] 0 48 attackerEntityNum 10 attackerEntityNum 10 49 iHeadIcon 4 apos.trDuration 32 50 iHeadIconTeam 2 constantLight 32 51 apos.trDuration 32 dmgFlags 32 52 time 32 pos.trTime 32 53 time2 32 pos.trDuration 32 54 origin2[0] 0 groundEntityNum 10 55 origin2[1] 0 legsAnim 10 56 origin2[2] 0 clientNum 8 57 angles2[2] 0 pos.trDelta[1] 0 58 constantLight 32 pos.trDelta[0] 0 59 dmgFlags 32 pos.trDelta[2] 0 Entity record grammar ~~~~~~~~~~~~~~~~~~~~~ The entity stream is sorted by number and terminated by the 10-bit value 1023. Every other record is: .. code-block:: text entityNumber 10 bits vehicleTable 1 bit removed 1 bit if !removed: changed 1 bit if changed: lastChanged 8 bits # count, 1..60 fieldDelta[0 .. lastChanged-1] An explicitly forced but unchanged record has ``changed=0``. An omitted unchanged record consumes no bits at all. Client-state descriptor table ----------------------------- The 32-byte name is transmitted as eight raw 32-bit lanes. The first dword of the native record, ``clientNum``, is the separate six-bit record number and is not a descriptor entry. .. code-block:: text Idx Field Bits 0 team 2 1 name[0] 32 2 name[4] 32 3 attachModelIndex[0] 8 4 attachModelIndex[2] 8 5 attachModelIndex[3] 8 6 modelindex 8 7 attachModelIndex[1] 8 8 name[8] 32 9 name[12] 32 10 attachModelIndex[4] 8 11 name[16] 32 12 attachTagIndex[0] 5 13 attachTagIndex[1] 5 14 attachTagIndex[2] 5 15 attachTagIndex[3] 5 16 attachTagIndex[4] 5 17 attachModelIndex[5] 8 18 attachTagIndex[5] 5 19 name[20] 32 20 name[24] 32 21 name[28] 32 Client record grammar ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: text recordPresent 1 bit # 0 terminates the client stream if recordPresent: clientNum 6 bits removed 1 bit if !removed: changed 1 bit if changed: lastChanged 8 bits # count, 1..22 fieldDelta[0 .. lastChanged-1] Player-state main descriptor table ---------------------------------- The 114 main descriptors are followed by stats, ammo, clips, objectives, and HUD arrays as described in the main protocol document. The player-state field codec differs from the generic codec by omitting the generic explicit-zero bit for ordinary integer and float fields. .. code-block:: text Idx Field Bits 0 commandTime 32 1 origin[1] 0 2 origin[0] 0 3 origin[2] 0 4 viewangles[1] -100 5 viewangles[0] -100 6 iCompassFriendInfo 32 7 eFlags 24 8 bobCycle 8 9 viewHeightCurrent 0 10 eventSequence 8 11 legsAnim 10 12 pm_flags 24 13 delta_angles[1] 16 14 velocity[1] 0 15 velocity[0] 0 16 iCompassTankInfo 32 17 speed 16 18 mins[0] 0 19 mins[1] 0 20 maxs[0] 0 21 maxs[1] 0 22 maxs[2] 0 23 proneViewHeight -8 24 crouchViewHeight -8 25 standViewHeight -8 26 deadViewHeight -8 27 walkSpeedScale 0 28 runSpeedScale 0 29 sprintSpeedScale 0 30 proneSpeedScale 0 31 crouchSpeedScale 0 32 strafeSpeedScale 0 33 backSpeedScale 0 34 leanSpeedScale 0 35 fatigueScale 0 36 lastSprintTime 0 37 friction 0 38 groundEntityNum 10 39 viewHeightTarget -8 40 events[0] 8 41 weapons[0] 32 42 weaponslots[0] 32 43 gravity 16 44 serverCursorHintString -8 45 viewmodelIndex 8 46 events[1] 8 47 events[2] 8 48 events[3] 8 49 delta_angles[0] 16 50 weapon 7 51 movementDir 8 52 viewHeightLerpTarget -8 53 weaponslots[4] 32 54 proneDirection 0 55 aimSpreadScale 0 56 weapAnim 10 57 jumpTime 32 58 velocity[2] 0 59 weaponTime -16 60 proneTorsoPitch 0 61 proneDirectionPitch 0 62 fTorsoPitch 0 63 fWaistPitch 0 64 fTorsoHeight 0 65 weaponstate 4 66 torsoTimer 16 67 torsoAnim 10 68 eventParms[0] 8 69 vLadderVec[0] 0 70 eventParms[3] 8 71 viewHeightLerpDown 1 72 weaponDelay -16 73 eventParms[1] 8 74 viewHeightLerpTime 32 75 eventParms[2] 8 76 vLadderVec[1] 0 77 fWeaponPosFrac 0 78 pm_type 8 79 legsTimer 16 80 fJumpOriginZ 0 81 leanf 0 82 damageEvent 8 83 damageYaw 8 84 damagePitch 8 85 damageCount 7 86 weaponrechamber[0] 32 87 grenadeTimeLeft -16 88 pm_time -16 89 iFoliageSoundTime 32 90 iFatigueSoundTime 32 91 deltaTime 32 92 serverCursorHint 8 93 serverCursorHintVal 8 94 shellshockIndex 4 95 shellshockTime 32 96 shellshockDuration 16 97 delta_angles[2] 16 98 vLadderVec[2] 0 99 clientNum 8 100 weapons[1] 32 101 weaponrechamber[1] 32 102 viewangles[2] -100 103 viewHeightLerpPosAdj 0 104 mins[2] 0 105 viewlocked 8 106 viewlocked_entNum 16 107 vehPos 4 108 vehType 4 109 vehMotion 2 110 weapons[2] 32 111 weapons[3] 32 112 weaponrechamber[2] 32 113 weaponrechamber[3] 32 Player-state auxiliary arrays ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Stats have these wire widths: ===== ======================== ===== Index Meaning Width ===== ======================== ===== 0 health 16 1 dead yaw 16 2 maximum health 16 3 identified client number 6 4 identified client health 16 5 spawn count 8 ===== ======================== ===== ``ammo[64]`` and ``clips[64]`` are divided into four consecutive groups of 16. Each transmitted group begins with a 16-bit changed mask, then one 16-bit value for every set bit in increasing item-index order. Objective descriptor table -------------------------- There are 16 objective records. The three-bit objective state is transmitted outside this table for every objective when the objective array is present. .. code-block:: text Idx Field Bits 0 origin[0] 0 1 origin[1] 0 2 origin[2] 0 3 icon 12 4 entNum 10 5 teamNum 4 Objective state values established by the game are ``EMPTY=0``, ``ACTIVE=1``, ``INVISIBLE=2``, and ``CURRENT=4``. The three-bit wire domain can represent unnamed values as well; parsers should validate according to their policy. HUD-element descriptor table ---------------------------- Player state contains two arrays of 63 HUD elements: archival first on the wire, then current. An element type of zero terminates the active prefix when the sender calculates the six-bit element count. .. code-block:: text Idx Field Bits 0 color.rgba 32 1 type 4 2 fontScale 0 3 y 10 4 x 10 5 alignY 2 6 alignX 2 7 time 32 8 font 4 9 text 8 10 shaderIndex 8 11 width 10 12 height 10 13 sort 0 14 fromColor.rgba 32 15 fadeStartTime 32 16 fadeTime 16 17 scaleStartTime 32 18 scaleTime 16 19 fromHeight 10 20 value 0 21 label 8 22 fromWidth 10 23 moveStartTime 32 24 moveTime 16 25 fromX 10 26 fromY 10 27 duration 32 28 SCOORD 32 29 TCOORD 32 The five-bit ``lastField`` value is an inclusive descriptor index. A valid value is therefore 0..29. Each element carries deltas for every descriptor from zero through ``lastField``; unchanged entries within that prefix still consume their changed bit. Generic delta record pseudocode ------------------------------- The following pseudocode summarizes the common entity/client record body. It does not include the entity vehicle-table bit or the client's stream-present bit: .. code-block:: text if removed: write_bit(1) return write_bit(0) lastChanged = one_past_highest_different_descriptor(base, value) if lastChanged == 0: write_bit(0) return write_bit(1) write_byte(lastChanged) for i in 0 .. lastChanged-1: write_field_delta(descriptor[i], base, value) Generic field pseudocode ~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: text if old_bits == new_bits: write_bit(0) return write_bit(1) if descriptor.bits == 0: # float if new_float == 0: write_bit(0) else: write_bit(1) if new_float is exact integer in [-4096, 4095]: write_bit(0) biased = new_integer + 4096 write_bits(biased, 5) write_byte(biased >> 5) else: write_bit(1) write_u32(raw_float_bits) return if descriptor.bits == -100: # angle16 if new_bits == 0: write_bit(0) else: write_bit(1) write_angle16(new_float) return if new_bits == 0: write_bit(0) else: write_bit(1) write_bits(new_bits, abs(descriptor.bits)) The physical writer emits a non-byte-multiple low remainder through the bit cursor, then emits complete bytes through the byte cursor. This is equivalent to ``write_bits`` only if the implementation reproduces the protocol's dual cursor behavior described in the main document. Player-state field pseudocode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The player-state main table changes the value body as follows: .. code-block:: text if old_bits == new_bits: write_bit(0) return write_bit(1) if descriptor.bits == 0: if new_float is exact integer in [-4096, 4095]: write_bit(0) biased = new_integer + 4096 write_bits(biased, 5) write_byte(biased >> 5) else: write_bit(1) write_u32(raw_float_bits) else if descriptor.bits == -100: write_bit(new_bits != 0) if new_bits != 0: write_angle16(new_float) else: write_bits(new_bits, abs(descriptor.bits)) Unlike the generic field codec, a changed ordinary integer can encode zero without another presence bit, and a changed float zero uses the compact integer form.