United Offensive protocol 22 wire-schema appendix

Purpose

This appendix contains the ordered constants required to implement the compressed message and snapshot codecs described in United Offensive 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:

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.

    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.

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:

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.

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

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.

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.

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.

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:

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

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:

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.