Entitiesยถ
Entities are the core data objects in a demo. Every game object โ players, NPCs, projectiles, world state โ is represented as an entity with a class, an index, and a set of fields.
Entity Structureยถ
An entity has:
Property |
Description |
|---|---|
|
Integer index (0โ16383), used to identify the entity in the bit stream |
|
Serial number, incremented when an index is reused |
|
Numeric class identifier |
|
Network class name (e.g., |
|
Hash map of packed field path keys to values |
Field Valuesยถ
Fields are stored as a FieldValue enum:
Variant |
Rust Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Lifecycleยถ
Entity state changes arrive in SVC_PacketEntities messages. The entity data is a
bit stream containing a sequence of updates, each prefixed with a delta header
(2 bits):
Header |
Meaning |
|---|---|
|
Update โ apply field deltas to an existing entity |
|
Create โ new entity with baseline + deltas |
|
Leave โ entity leaving (removed from tracking) |
|
Delete โ entity removed |
Entity indices are delta-encoded: each update adds read_ubitvar() + 1 to the
previous index, so only the gaps between changed entities need to be stored.
Creation Flowยถ
When a new entity is created:
Read
class_id(N bits, where N is from class info) andserial(17 bits)Look up the class in class info to get the
network_nameLook up the serializer by network name
Check the baseline cache โ if this class was seen before, clone the cached entity
Otherwise, create a fresh entity and apply the instance baseline from the string table, then cache it
Apply the creation delta (field changes specific to this entity) on top
This baseline + delta approach means the wire format only needs to carry the differences from the class default, saving significant bandwidth.
Update Flowยถ
Updates are simpler:
Look up the existing entity by index
Get its serializer by class name
Read and apply field path deltas
Field Pathsยถ
Field updates use field paths โ hierarchical indices that identify which field changed. A field path is a sequence of up to 7 indices, one per level of nesting:
[3] โ 4th top-level field
[3, 2] โ 3rd sub-field of the 4th top-level field
[3, 2, 0] โ 1st sub-sub-field
Field paths are Huffman-encoded in the bit stream. The Huffman tree uses 41 operation types optimized for common patterns:
PlusOne / PlusN: increment the current levelโs index (common for sequential field updates)
Push: descend to a child level
Pop: ascend one or more levels
NonTopo: jump to a non-sequential index
FieldPathEncodeFinish: end of field path list
Each field path is packed into a 64-bit key for storage in the entityโs field map.
Filtered Parsingยถ
For performance, the parser supports filtered parsing where only entities of
specified classes are fully tracked. Non-matching entities are still parsed (the bit
reader must advance correctly) but their field values are discarded. This is how
get_player_ticks() can efficiently process only CCitadelPlayerPawn and
CCitadelPlayerController entities while skipping everything else.
Entity Handlesยถ
Some fields store entity handles โ references to other entities. A handle is a 32-bit value where the lower 15 bits encode the entity index:
entity_index = handle & 0x7FFF
This is how CCitadelPlayerController references its CCitadelPlayerPawn via the
m_hPawn field. The parser resolves this by extracting the index and looking up the
corresponding entity.