Skip to content

Field Decoders

Dispatches field types to concrete decoders, including quantized float handling and packed value decoding.

See also: Field Decoders

gem.field_decoder.find_decoder(field: _FieldLike) -> FieldDecoder

Return the appropriate decoder for the given field.

Dispatch order: 1. Type factories (QAngle, float32, Vector, …) — need field parameters 2. Name overrides 3. Direct type → decoder table 4. Default (varuint32)

Parameters:

Name Type Description Default
field _FieldLike

An object with field_type.base_type, encoder, bit_count, encode_flags, low_value, high_value, and var_name attributes.

required

Returns:

Type Description
FieldDecoder

A callable (BitReader) -> value.

Source code in src/gem/field_decoder.py
def find_decoder(field: _FieldLike) -> FieldDecoder:
    """Return the appropriate decoder for the given field.

    Dispatch order:
    1. Type factories (QAngle, float32, Vector, …) — need field parameters
    2. Name overrides
    3. Direct type → decoder table
    4. Default (varuint32)

    Args:
        field: An object with ``field_type.base_type``, ``encoder``,
            ``bit_count``, ``encode_flags``, ``low_value``, ``high_value``,
            and ``var_name`` attributes.

    Returns:
        A callable ``(BitReader) -> value``.
    """
    base_type: str = field.field_type.base_type  # type: ignore[attr-defined]

    if base_type in _FIELD_TYPE_FACTORIES:
        return _FIELD_TYPE_FACTORIES[base_type](field)

    var_name: str = getattr(field, "var_name", "")
    if var_name in _FIELD_NAME_DECODERS:
        return _FIELD_NAME_DECODERS[var_name]

    if base_type in _FIELD_TYPE_DECODERS:
        return _FIELD_TYPE_DECODERS[base_type]

    return default_decoder

gem.field_decoder.find_decoder_by_base_type(base_type: str) -> FieldDecoder

Return a decoder for a base type string without field context.

Used for variable-array child elements where no field object is available.

Parameters:

Name Type Description Default
base_type str

The C++ type name string (e.g. "uint32").

required

Returns:

Type Description
FieldDecoder

A callable (BitReader) -> value.

Source code in src/gem/field_decoder.py
def find_decoder_by_base_type(base_type: str) -> FieldDecoder:
    """Return a decoder for a base type string without field context.

    Used for variable-array child elements where no field object is available.

    Args:
        base_type: The C++ type name string (e.g. ``"uint32"``).

    Returns:
        A callable ``(BitReader) -> value``.
    """
    return _FIELD_TYPE_DECODERS.get(base_type, default_decoder)

gem.field_decoder.QuantizedFloatDecoder

Decoder for Source 2 quantized floats (CNetworkedQuantizedFloat).

Encodes a float in a fixed bit-width with optional round-up/down and zero-preservation flags. Parameters are derived from the field's send-table entry.

Parameters:

Name Type Description Default
bit_count int | None

Number of bits used to encode the value, or None/0/>=32 for no-scale (raw 32-bit IEEE float).

required
flags int | None

Bitmask of QFF_* flags, or None for 0.

required
low_value float | None

Minimum representable value, or None for 0.0.

required
high_value float | None

Maximum representable value, or None for 1.0.

required
Source code in src/gem/field_decoder.py
class QuantizedFloatDecoder:
    """Decoder for Source 2 quantized floats (CNetworkedQuantizedFloat).

    Encodes a float in a fixed bit-width with optional round-up/down and
    zero-preservation flags.  Parameters are derived from the field's
    send-table entry.

    Args:
        bit_count: Number of bits used to encode the value, or None/0/>=32
            for no-scale (raw 32-bit IEEE float).
        flags: Bitmask of QFF_* flags, or None for 0.
        low_value: Minimum representable value, or None for 0.0.
        high_value: Maximum representable value, or None for 1.0.
    """

    __slots__ = (
        "low",
        "high",
        "high_low_mul",
        "dec_mul",
        "bitcount",
        "flags",
        "no_scale",
    )

    def __init__(
        self,
        bit_count: int | None,
        flags: int | None,
        low_value: float | None,
        high_value: float | None,
    ) -> None:
        bc = bit_count or 0
        if bc == 0 or bc >= 32:
            self.no_scale = True
            self.bitcount = 32
            self.low = self.high = self.high_low_mul = self.dec_mul = 0.0
            self.flags = 0
            return

        self.no_scale = False
        self.bitcount = bc
        self.low = low_value if low_value is not None else 0.0
        self.high = high_value if high_value is not None else 1.0
        self.flags = flags if flags is not None else 0

        self._validate_flags()

        steps = 1 << self.bitcount
        range_ = self.high - self.low

        if self.flags & _QFF_ROUNDDOWN:
            self.high -= range_ / steps
        elif self.flags & _QFF_ROUNDUP:
            self.low += range_ / steps

        if self.flags & _QFF_ENCODE_INTEGERS:
            delta = max(self.high - self.low, 1.0)
            delta_log2 = math.ceil(math.log2(delta))
            range2 = 1 << int(delta_log2)
            bc2 = self.bitcount
            while (1 << bc2) <= range2:
                bc2 += 1
            if bc2 > self.bitcount:
                self.bitcount = bc2
                steps = 1 << self.bitcount
            offset = range2 / steps
            self.high = self.low + range2 - offset

        self._assign_multipliers(steps)

        # Remove flags that are already satisfied by the multiplier
        if self.flags & _QFF_ROUNDDOWN and self._quantize(self.low) == self.low:
            self.flags &= ~_QFF_ROUNDDOWN
        if self.flags & _QFF_ROUNDUP and self._quantize(self.high) == self.high:
            self.flags &= ~_QFF_ROUNDUP
        if self.flags & _QFF_ENCODE_ZERO and self._quantize(0.0) == 0.0:
            self.flags &= ~_QFF_ENCODE_ZERO

    def _validate_flags(self) -> None:
        if not self.flags:
            return
        if (self.low == 0.0 and self.flags & _QFF_ROUNDDOWN) or (
            self.high == 0.0 and self.flags & _QFF_ROUNDUP
        ):
            self.flags &= ~_QFF_ENCODE_ZERO
        if self.low == 0.0 and self.flags & _QFF_ENCODE_ZERO:
            self.flags |= _QFF_ROUNDDOWN
            self.flags &= ~_QFF_ENCODE_ZERO
        if self.high == 0.0 and self.flags & _QFF_ENCODE_ZERO:
            self.flags |= _QFF_ROUNDUP
            self.flags &= ~_QFF_ENCODE_ZERO
        if self.low > 0.0 or self.high < 0.0:
            self.flags &= ~_QFF_ENCODE_ZERO
        if self.flags & _QFF_ENCODE_INTEGERS:
            self.flags &= ~(_QFF_ROUNDUP | _QFF_ROUNDDOWN | _QFF_ENCODE_ZERO)

    def _assign_multipliers(self, steps: int) -> None:
        range_ = self.high - self.low
        high_int = 0xFFFFFFFE if self.bitcount == 32 else (1 << self.bitcount) - 1
        high_mul = float(high_int) if abs(range_) <= 0.0 else high_int / range_
        # Adjust precision to avoid overflow
        if high_mul * range_ > high_int:
            for mult in (0.9999, 0.99, 0.9, 0.8, 0.7):
                high_mul = high_int / range_ * mult
                if high_mul * range_ <= high_int:
                    break
        self.high_low_mul = high_mul
        self.dec_mul = 1.0 / (steps - 1)

    def _quantize(self, val: float) -> float:
        i = int((val - self.low) * self.high_low_mul)
        return self.low + (self.high - self.low) * (i * self.dec_mul)

    def decode(self, r: BitReader) -> float:
        """Read and decode one quantized float from r.

        Args:
            r: BitReader positioned at the start of the encoded value.

        Returns:
            Decoded float value.
        """
        if self.no_scale:
            return struct.unpack("<f", struct.pack("<I", r.read_bits(32)))[0]
        if self.flags & _QFF_ROUNDDOWN and r.read_boolean():
            return self.low
        if self.flags & _QFF_ROUNDUP and r.read_boolean():
            return self.high
        if self.flags & _QFF_ENCODE_ZERO and r.read_boolean():
            return 0.0
        return self.low + (self.high - self.low) * r.read_bits(self.bitcount) * self.dec_mul

decode(r: BitReader) -> float

Read and decode one quantized float from r.

Parameters:

Name Type Description Default
r BitReader

BitReader positioned at the start of the encoded value.

required

Returns:

Type Description
float

Decoded float value.

Source code in src/gem/field_decoder.py
def decode(self, r: BitReader) -> float:
    """Read and decode one quantized float from r.

    Args:
        r: BitReader positioned at the start of the encoded value.

    Returns:
        Decoded float value.
    """
    if self.no_scale:
        return struct.unpack("<f", struct.pack("<I", r.read_bits(32)))[0]
    if self.flags & _QFF_ROUNDDOWN and r.read_boolean():
        return self.low
    if self.flags & _QFF_ROUNDUP and r.read_boolean():
        return self.high
    if self.flags & _QFF_ENCODE_ZERO and r.read_boolean():
        return 0.0
    return self.low + (self.high - self.low) * r.read_bits(self.bitcount) * self.dec_mul