Skip to main content

boon/entity/
field_decoder.rs

1use crate::error::Result;
2use crate::io::BitReader;
3
4use super::field_value::FieldValue;
5use super::quantized_float::QuantizedFloat;
6
7/// Mutable state shared across field decoders during a single parse pass.
8pub struct FieldDecodeContext {
9    /// Current tick interval; used by the simulation-time decoder.
10    pub tick_interval: f32,
11    /// Reusable buffer for string decoding (avoids per-field allocations).
12    pub string_buf: Vec<u8>,
13}
14
15impl FieldDecodeContext {
16    pub fn new(tick_interval: f32) -> Self {
17        Self {
18            tick_interval,
19            string_buf: Vec::with_capacity(512),
20        }
21    }
22}
23
24/// Describes how to read a single field value from a [`BitReader`](crate::io::BitReader).
25///
26/// Each variant corresponds to a Source 2 wire encoding. The correct
27/// variant is chosen at parse time by [`get_field_metadata`].
28#[derive(Debug, Clone)]
29pub enum Decoder {
30    Bool,
31    I64,
32    U64,
33    U64Fixed64,
34    F32NoScale,
35    F32SimulationTime,
36    F32Coord,
37    F32Normal,
38    F32Quantized(QuantizedFloat),
39    String,
40    Vector2(Box<Decoder>),
41    Vector3(Box<Decoder>),
42    Vector3Normal,
43    Vector4(Box<Decoder>),
44    /// Fixed-count float vector for types wider than 4 components (e.g.
45    /// `Quaternion` = 4, `CTransform` = 6). Each component uses `inner`.
46    FloatVecN {
47        count: usize,
48        inner: Box<Decoder>,
49    },
50    /// Length-prefixed byte blob (`CUtlBinaryBlock`): a varint byte count
51    /// followed by that many raw bytes.
52    BinaryBlock,
53    /// Polymorphic pointer leaf (e.g. `m_pGameModeRules`): a presence bool plus
54    /// a ubitvar selecting which concrete sub-serializer is active. We keep the
55    /// bool as the field value; the selector affects deeper field paths.
56    Poly,
57    QAnglePitchYaw {
58        bit_count: usize,
59    },
60    QAnglePrecise,
61    QAngleBitCount {
62        bit_count: usize,
63    },
64    QAngleCoord,
65    /// Used as a placeholder/invalid decoder.
66    Default,
67}
68
69impl Decoder {
70    /// Read a single field value from the bitstream.
71    pub fn decode(&self, ctx: &mut FieldDecodeContext, br: &mut BitReader) -> Result<FieldValue> {
72        match self {
73            Decoder::Bool => Ok(FieldValue::Bool(br.read_bool()?)),
74
75            Decoder::I64 => Ok(FieldValue::I64(br.read_varint64()?)),
76
77            Decoder::U64 => Ok(FieldValue::U64(br.read_uvarint64()?)),
78
79            Decoder::U64Fixed64 => {
80                let mut buf = [0u8; 8];
81                br.read_bytes(&mut buf)?;
82                Ok(FieldValue::U64(u64::from_le_bytes(buf)))
83            }
84
85            Decoder::F32NoScale => Ok(FieldValue::F32(br.read_f32()?)),
86
87            Decoder::F32SimulationTime => {
88                let ticks = br.read_uvarint32()?;
89                Ok(FieldValue::F32(ticks as f32 * ctx.tick_interval))
90            }
91
92            Decoder::F32Coord => Ok(FieldValue::F32(br.read_bitcoord()?)),
93
94            Decoder::F32Normal => Ok(FieldValue::F32(br.read_bitnormal()?)),
95
96            Decoder::F32Quantized(qf) => Ok(FieldValue::F32(qf.decode(br)?)),
97
98            Decoder::String => {
99                ctx.string_buf.clear();
100                br.read_string_raw(&mut ctx.string_buf)?;
101                Ok(FieldValue::String(ctx.string_buf.clone()))
102            }
103
104            Decoder::Vector2(inner) => {
105                let x = inner.decode_f32(ctx, br)?;
106                let y = inner.decode_f32(ctx, br)?;
107                Ok(FieldValue::Vector2([x, y]))
108            }
109
110            Decoder::Vector3(inner) => {
111                let x = inner.decode_f32(ctx, br)?;
112                let y = inner.decode_f32(ctx, br)?;
113                let z = inner.decode_f32(ctx, br)?;
114                Ok(FieldValue::Vector3([x, y, z]))
115            }
116
117            Decoder::Vector3Normal => Ok(FieldValue::Vector3(br.read_bitvec3normal()?)),
118
119            Decoder::FloatVecN { count, inner } => {
120                let mut v = Vec::with_capacity(*count);
121                for _ in 0..*count {
122                    v.push(inner.decode_f32(ctx, br)?);
123                }
124                Ok(FieldValue::FloatVector(v))
125            }
126
127            Decoder::BinaryBlock => {
128                let n = br.read_uvarint32()? as usize;
129                ctx.string_buf.clear();
130                ctx.string_buf.resize(n, 0);
131                br.read_bytes(&mut ctx.string_buf)?;
132                Ok(FieldValue::String(ctx.string_buf.clone()))
133            }
134
135            Decoder::Poly => {
136                let present = br.read_bool()?;
137                let _poly_type_index = br.read_ubitvar()?;
138                Ok(FieldValue::Bool(present))
139            }
140
141            Decoder::Vector4(inner) => {
142                let x = inner.decode_f32(ctx, br)?;
143                let y = inner.decode_f32(ctx, br)?;
144                let z = inner.decode_f32(ctx, br)?;
145                let w = inner.decode_f32(ctx, br)?;
146                Ok(FieldValue::Vector4([x, y, z, w]))
147            }
148
149            Decoder::QAnglePitchYaw { bit_count } => {
150                let pitch = br.read_bitangle(*bit_count)?;
151                let yaw = br.read_bitangle(*bit_count)?;
152                Ok(FieldValue::QAngle([pitch, yaw, 0.0]))
153            }
154
155            Decoder::QAnglePrecise => {
156                let mut v = [0.0f32; 3];
157                let rx = br.read_bool()?;
158                let ry = br.read_bool()?;
159                let rz = br.read_bool()?;
160                if rx {
161                    v[0] = br.read_bitangle(20)?;
162                }
163                if ry {
164                    v[1] = br.read_bitangle(20)?;
165                }
166                if rz {
167                    v[2] = br.read_bitangle(20)?;
168                }
169                Ok(FieldValue::QAngle(v))
170            }
171
172            Decoder::QAngleBitCount { bit_count } => {
173                let x = br.read_bitangle(*bit_count)?;
174                let y = br.read_bitangle(*bit_count)?;
175                let z = br.read_bitangle(*bit_count)?;
176                Ok(FieldValue::QAngle([x, y, z]))
177            }
178
179            Decoder::QAngleCoord => Ok(FieldValue::QAngle(br.read_bitvec3coord()?)),
180
181            Decoder::Default => Ok(FieldValue::U64(br.read_uvarint64()?)),
182        }
183    }
184
185    /// Helper to decode a field value as f32 (used by vector decoders).
186    fn decode_f32(&self, ctx: &mut FieldDecodeContext, br: &mut BitReader) -> Result<f32> {
187        match self.decode(ctx, br)? {
188            FieldValue::F32(v) => Ok(v),
189            _ => Ok(0.0),
190        }
191    }
192
193    /// Skip a field value without fully decoding it - just advances the bit reader.
194    /// This is faster than decode() when we don't need the value.
195    #[allow(clippy::only_used_in_recursion)]
196    pub fn skip(&self, ctx: &mut FieldDecodeContext, br: &mut BitReader) -> Result<()> {
197        match self {
198            Decoder::Bool => {
199                br.skip_bits(1)?;
200            }
201
202            Decoder::I64 => {
203                br.skip_varint()?;
204            }
205
206            Decoder::U64 => {
207                br.skip_varint()?;
208            }
209
210            Decoder::U64Fixed64 => {
211                br.skip_bits(64)?;
212            }
213
214            Decoder::F32NoScale => {
215                br.skip_bits(32)?;
216            }
217
218            Decoder::F32SimulationTime => {
219                br.skip_varint()?;
220            }
221
222            Decoder::F32Coord => {
223                br.skip_bitcoord()?;
224            }
225
226            Decoder::F32Normal => {
227                br.skip_bitnormal()?;
228            }
229
230            Decoder::F32Quantized(qf) => {
231                qf.skip(br)?;
232            }
233
234            Decoder::String => {
235                br.skip_string()?;
236            }
237
238            Decoder::Vector2(inner) => {
239                inner.skip(ctx, br)?;
240                inner.skip(ctx, br)?;
241            }
242
243            Decoder::Vector3(inner) => {
244                inner.skip(ctx, br)?;
245                inner.skip(ctx, br)?;
246                inner.skip(ctx, br)?;
247            }
248
249            Decoder::Vector3Normal => {
250                br.skip_bitvec3normal()?;
251            }
252
253            Decoder::FloatVecN { count, inner } => {
254                for _ in 0..*count {
255                    inner.skip(ctx, br)?;
256                }
257            }
258
259            Decoder::BinaryBlock => {
260                let n = br.read_uvarint32()? as usize;
261                br.skip_bits(n * 8)?;
262            }
263
264            Decoder::Poly => {
265                br.skip_bits(1)?;
266                br.read_ubitvar()?;
267            }
268
269            Decoder::Vector4(inner) => {
270                inner.skip(ctx, br)?;
271                inner.skip(ctx, br)?;
272                inner.skip(ctx, br)?;
273                inner.skip(ctx, br)?;
274            }
275
276            Decoder::QAnglePitchYaw { bit_count } => {
277                br.skip_bits(*bit_count * 2)?;
278            }
279
280            Decoder::QAnglePrecise => {
281                let rx = br.read_bool()?;
282                let ry = br.read_bool()?;
283                let rz = br.read_bool()?;
284                if rx {
285                    br.skip_bits(20)?;
286                }
287                if ry {
288                    br.skip_bits(20)?;
289                }
290                if rz {
291                    br.skip_bits(20)?;
292                }
293            }
294
295            Decoder::QAngleBitCount { bit_count } => {
296                br.skip_bits(*bit_count * 3)?;
297            }
298
299            Decoder::QAngleCoord => {
300                br.skip_bitvec3coord()?;
301            }
302
303            Decoder::Default => {
304                br.skip_varint()?;
305            }
306        }
307        Ok(())
308    }
309}
310
311/// Special descriptor for fields that need non-standard handling
312/// (arrays, pointers, or nested serializers).
313#[derive(Debug, Clone)]
314pub enum FieldSpecialDescriptor {
315    /// Fixed-length array (e.g. `int32[4]`).
316    FixedArray { length: usize },
317    /// Variable-length array of a primitive type (e.g. `CNetworkUtlVectorBase<int32>`).
318    DynamicArray { inner_decoder: Decoder },
319    /// Variable-length array whose elements have a nested serializer.
320    DynamicSerializerArray,
321    /// Pointer / entity handle (encoded as a single boolean "present" flag).
322    Pointer,
323}
324
325/// Metadata about how to decode a field.
326#[derive(Debug, Clone)]
327pub struct FieldMetadata {
328    pub decoder: Decoder,
329    pub special: Option<FieldSpecialDescriptor>,
330}
331
332impl Default for FieldMetadata {
333    fn default() -> Self {
334        Self {
335            decoder: Decoder::Default,
336            special: None,
337        }
338    }
339}
340
341impl FieldMetadata {
342    pub fn is_dynamic_array(&self) -> bool {
343        matches!(
344            self.special,
345            Some(FieldSpecialDescriptor::DynamicArray { .. })
346                | Some(FieldSpecialDescriptor::DynamicSerializerArray)
347        )
348    }
349
350    pub fn is_fixed_array(&self) -> bool {
351        matches!(
352            self.special,
353            Some(FieldSpecialDescriptor::FixedArray { .. })
354        )
355    }
356
357    pub fn fixed_array_length(&self) -> Option<usize> {
358        match &self.special {
359            Some(FieldSpecialDescriptor::FixedArray { length }) => Some(*length),
360            _ => None,
361        }
362    }
363
364    pub fn is_dynamic_serializer_array(&self) -> bool {
365        matches!(
366            self.special,
367            Some(FieldSpecialDescriptor::DynamicSerializerArray)
368        )
369    }
370
371    pub fn is_pointer(&self) -> bool {
372        matches!(self.special, Some(FieldSpecialDescriptor::Pointer))
373    }
374
375    pub fn dynamic_array_inner_metadata(&self) -> FieldMetadata {
376        match &self.special {
377            Some(FieldSpecialDescriptor::DynamicArray { inner_decoder }) => FieldMetadata {
378                decoder: inner_decoder.clone(),
379                special: None,
380            },
381            _ => FieldMetadata::default(),
382        }
383    }
384}
385
386/// Build a float decoder based on field properties.
387fn build_f32_decoder(
388    var_name: &str,
389    bit_count: Option<i32>,
390    low_value: Option<f32>,
391    high_value: Option<f32>,
392    encode_flags: Option<i32>,
393    var_encoder: Option<&str>,
394) -> Decoder {
395    // Simulation time special case
396    if var_name == "m_flSimulationTime" || var_name == "m_flAnimTime" {
397        return Decoder::F32SimulationTime;
398    }
399
400    // Check var_encoder
401    if let Some(encoder) = var_encoder {
402        match encoder {
403            "coord" => return Decoder::F32Coord,
404            "normal" => return Decoder::F32Normal,
405            _ => {}
406        }
407    }
408
409    let bc = bit_count.unwrap_or(0);
410    if bc == 0 || bc == 32 {
411        return Decoder::F32NoScale;
412    }
413
414    // Quantized float
415    match QuantizedFloat::new(
416        bc,
417        encode_flags.unwrap_or(0),
418        low_value.unwrap_or(0.0),
419        high_value.unwrap_or(0.0),
420    ) {
421        Ok(qf) => Decoder::F32Quantized(qf),
422        Err(_) => Decoder::F32NoScale,
423    }
424}
425
426/// Determine the [`FieldMetadata`] (decoder + special descriptor) for a serializer field.
427///
428/// This is the main dispatch function that maps Source 2 network field descriptions
429/// to the correct binary decoder. It inspects the type string, field name, and
430/// encoder hints to choose the appropriate [`Decoder`] variant and, when the field
431/// represents an array or pointer, attaches a [`FieldSpecialDescriptor`].
432///
433/// # Parameters
434///
435/// * `var_type` — the Source 2 type name (e.g. `"int32"`, `"Vector"`, `"CBaseEntity*"`,
436///   `"CNetworkUtlVectorBase< float32 >"`). Pointer suffix (`*`), array brackets
437///   (`[N]`), and generic angle brackets (`< T >`) are all handled.
438/// * `var_name` — the field name (e.g. `"m_flSimulationTime"`). Certain names trigger
439///   special-case decoders.
440/// * `bit_count` — optional bit width from the serializer; used for quantized floats
441///   and `QAngle` variants.
442/// * `low_value` / `high_value` — optional range bounds for quantized float encoding.
443/// * `encode_flags` — optional flags passed to [`QuantizedFloat`] when constructing a
444///   quantized decoder.
445/// * `var_encoder` — optional encoder hint string (e.g. `"coord"`, `"normal"`,
446///   `"qangle_pitch_yaw"`, `"fixed64"`).
447/// * `has_field_serializer` — `true` when the field carries a nested serializer,
448///   which upgrades dynamic arrays to [`FieldSpecialDescriptor::DynamicSerializerArray`].
449#[allow(clippy::too_many_arguments)]
450pub fn get_field_metadata(
451    var_type: &str,
452    var_name: &str,
453    bit_count: Option<i32>,
454    low_value: Option<f32>,
455    high_value: Option<f32>,
456    encode_flags: Option<i32>,
457    var_encoder: Option<&str>,
458    has_field_serializer: bool,
459) -> FieldMetadata {
460    // Parse the type to determine category
461    let trimmed = var_type.trim();
462
463    // Pointer types
464    if trimmed.ends_with('*') {
465        return FieldMetadata {
466            decoder: Decoder::Bool,
467            special: Some(FieldSpecialDescriptor::Pointer),
468        };
469    }
470
471    // Array types: type[length]
472    if let Some(bracket_pos) = trimmed.find('[')
473        && trimmed.ends_with(']')
474    {
475        let base = trimmed[..bracket_pos].trim();
476        let len_str = trimmed[bracket_pos + 1..trimmed.len() - 1].trim();
477
478        // char[N] is a string
479        if base == "char" {
480            return FieldMetadata {
481                decoder: Decoder::String,
482                special: None,
483            };
484        }
485
486        let length = len_str.parse::<usize>().unwrap_or(match len_str {
487            "MAX_ABILITY_DRAFT_ABILITIES" => 48,
488            "DOTA_ABILITY_DRAFT_HEROES_PER_GAME" => 10,
489            _ => 64,
490        });
491
492        let inner = get_field_metadata(
493            base,
494            var_name,
495            bit_count,
496            low_value,
497            high_value,
498            encode_flags,
499            var_encoder,
500            has_field_serializer,
501        );
502
503        return FieldMetadata {
504            decoder: inner.decoder,
505            special: Some(FieldSpecialDescriptor::FixedArray { length }),
506        };
507    }
508
509    // Generic/template types: CNetworkUtlVectorBase< T >
510    if let Some(angle_pos) = trimmed.find('<')
511        && let Some(close_pos) = trimmed.rfind('>')
512    {
513        let base = trimmed[..angle_pos].trim();
514        let inner_type = trimmed[angle_pos + 1..close_pos].trim();
515
516        let is_vector_base = matches!(
517            base,
518            "CNetworkUtlVectorBase" | "CUtlVectorEmbeddedNetworkVar" | "CUtlVector"
519        );
520
521        if is_vector_base {
522            if has_field_serializer {
523                return FieldMetadata {
524                    decoder: Decoder::U64,
525                    special: Some(FieldSpecialDescriptor::DynamicSerializerArray),
526                };
527            }
528
529            let inner = get_field_metadata(
530                inner_type,
531                var_name,
532                bit_count,
533                low_value,
534                high_value,
535                encode_flags,
536                var_encoder,
537                has_field_serializer,
538            );
539
540            return FieldMetadata {
541                decoder: Decoder::U64,
542                special: Some(FieldSpecialDescriptor::DynamicArray {
543                    inner_decoder: inner.decoder,
544                }),
545            };
546        }
547
548        // For non-vector templates, decode as the base type
549        return get_field_metadata(
550            base,
551            var_name,
552            bit_count,
553            low_value,
554            high_value,
555            encode_flags,
556            var_encoder,
557            has_field_serializer,
558        );
559    }
560
561    // Identify the base type
562    match trimmed {
563        // Primitives
564        "int8" | "int16" | "int32" | "int64" => FieldMetadata {
565            decoder: Decoder::I64,
566            special: None,
567        },
568
569        "bool" => FieldMetadata {
570            decoder: Decoder::Bool,
571            special: None,
572        },
573
574        "float32" | "CNetworkedQuantizedFloat" | "GameTime_t" => {
575            let decoder = build_f32_decoder(
576                var_name,
577                bit_count,
578                low_value,
579                high_value,
580                encode_flags,
581                var_encoder,
582            );
583            FieldMetadata {
584                decoder,
585                special: None,
586            }
587        }
588
589        // Pointer types: entity body/component handles transmitted as a
590        // single boolean "present" flag on the wire.
591        "CBodyComponentDCGBaseAnimating"
592        | "CBodyComponentBaseAnimating"
593        | "CBodyComponentBaseAnimatingOverlay"
594        | "CBodyComponentBaseModelEntity"
595        | "CBodyComponent"
596        | "CBodyComponentSkeletonInstance"
597        | "CBodyComponentPoint"
598        | "CLightComponent"
599        | "CRenderComponent"
600        | "C_BodyComponentBaseAnimating"
601        | "C_BodyComponentBaseAnimatingOverlay"
602        | "CPhysicsComponent" => FieldMetadata {
603            decoder: Decoder::Bool,
604            special: Some(FieldSpecialDescriptor::Pointer),
605        },
606
607        // String types
608        "CUtlSymbolLarge" | "CUtlString" | "CGlobalSymbol" | "char" => FieldMetadata {
609            decoder: Decoder::String,
610            special: None,
611        },
612
613        // Length-prefixed byte blob.
614        "CUtlBinaryBlock" => FieldMetadata {
615            decoder: Decoder::BinaryBlock,
616            special: None,
617        },
618
619        // Wide float vectors (more than 4 components).
620        "Quaternion" | "CTransform" => {
621            let count = if trimmed == "CTransform" { 6 } else { 4 };
622            let inner = build_f32_decoder(
623                var_name,
624                bit_count,
625                low_value,
626                high_value,
627                encode_flags,
628                var_encoder,
629            );
630            FieldMetadata {
631                decoder: Decoder::FloatVecN {
632                    count,
633                    inner: Box::new(inner),
634                },
635                special: None,
636            }
637        }
638
639        // Angle type
640        "QAngle" => {
641            let bc = bit_count.unwrap_or(0) as usize;
642            let decoder = if let Some(encoder) = var_encoder {
643                match encoder {
644                    "qangle_pitch_yaw" => Decoder::QAnglePitchYaw { bit_count: bc },
645                    "qangle_precise" => Decoder::QAnglePrecise,
646                    _ => {
647                        if bc == 0 {
648                            Decoder::QAngleCoord
649                        } else {
650                            Decoder::QAngleBitCount { bit_count: bc }
651                        }
652                    }
653                }
654            } else if bc == 0 {
655                Decoder::QAngleCoord
656            } else {
657                Decoder::QAngleBitCount { bit_count: bc }
658            };
659            FieldMetadata {
660                decoder,
661                special: None,
662            }
663        }
664
665        // Vector types
666        "Vector" | "VectorWS" => {
667            if var_encoder == Some("normal") {
668                FieldMetadata {
669                    decoder: Decoder::Vector3Normal,
670                    special: None,
671                }
672            } else {
673                let inner = build_f32_decoder(
674                    var_name,
675                    bit_count,
676                    low_value,
677                    high_value,
678                    encode_flags,
679                    var_encoder,
680                );
681                FieldMetadata {
682                    decoder: Decoder::Vector3(Box::new(inner)),
683                    special: None,
684                }
685            }
686        }
687
688        "Vector2D" => {
689            let inner = build_f32_decoder(
690                var_name,
691                bit_count,
692                low_value,
693                high_value,
694                encode_flags,
695                var_encoder,
696            );
697            FieldMetadata {
698                decoder: Decoder::Vector2(Box::new(inner)),
699                special: None,
700            }
701        }
702
703        "Vector4D" => {
704            let inner = build_f32_decoder(
705                var_name,
706                bit_count,
707                low_value,
708                high_value,
709                encode_flags,
710                var_encoder,
711            );
712            FieldMetadata {
713                decoder: Decoder::Vector4(Box::new(inner)),
714                special: None,
715            }
716        }
717
718        // Dynamic serializer arrays (special cases)
719        "m_SpeechBubbles" | "DOTA_CombatLogQueryProgress" => FieldMetadata {
720            decoder: Decoder::U64,
721            special: Some(FieldSpecialDescriptor::DynamicSerializerArray),
722        },
723
724        // Default: unsigned integer
725        _ => {
726            let decoder = if var_encoder == Some("fixed64") {
727                Decoder::U64Fixed64
728            } else {
729                Decoder::U64
730            };
731            FieldMetadata {
732                decoder,
733                special: None,
734            }
735        }
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742    use crate::io::BitReader;
743
744    fn meta(var_type: &str, var_name: &str) -> FieldMetadata {
745        get_field_metadata(var_type, var_name, None, None, None, None, None, false)
746    }
747
748    #[allow(clippy::too_many_arguments)]
749    fn meta_full(
750        var_type: &str,
751        var_name: &str,
752        bit_count: Option<i32>,
753        low: Option<f32>,
754        high: Option<f32>,
755        encode_flags: Option<i32>,
756        var_encoder: Option<&str>,
757        has_fs: bool,
758    ) -> FieldMetadata {
759        get_field_metadata(
760            var_type,
761            var_name,
762            bit_count,
763            low,
764            high,
765            encode_flags,
766            var_encoder,
767            has_fs,
768        )
769    }
770
771    // ── get_field_metadata dispatch ──
772
773    #[test]
774    fn pointer_type() {
775        let m = meta("CBaseEntity*", "m_hOwner");
776        assert!(matches!(m.decoder, Decoder::Bool));
777        assert!(m.is_pointer());
778    }
779
780    #[test]
781    fn bool_type() {
782        let m = meta("bool", "m_bActive");
783        assert!(matches!(m.decoder, Decoder::Bool));
784        assert!(m.special.is_none());
785    }
786
787    #[test]
788    fn int32_type() {
789        let m = meta("int32", "m_iHealth");
790        assert!(matches!(m.decoder, Decoder::I64));
791    }
792
793    #[test]
794    fn float32_no_scale() {
795        let m = meta("float32", "m_flValue");
796        assert!(matches!(m.decoder, Decoder::F32NoScale));
797    }
798
799    #[test]
800    fn simulation_time() {
801        let m = meta("float32", "m_flSimulationTime");
802        assert!(matches!(m.decoder, Decoder::F32SimulationTime));
803    }
804
805    #[test]
806    fn coord_encoder() {
807        let m = meta_full(
808            "float32",
809            "m_x",
810            None,
811            None,
812            None,
813            None,
814            Some("coord"),
815            false,
816        );
817        assert!(matches!(m.decoder, Decoder::F32Coord));
818    }
819
820    #[test]
821    fn quantized_float() {
822        let m = meta_full(
823            "float32",
824            "m_val",
825            Some(8),
826            Some(0.0),
827            Some(255.0),
828            None,
829            None,
830            false,
831        );
832        assert!(matches!(m.decoder, Decoder::F32Quantized(_)));
833    }
834
835    #[test]
836    fn string_utl_symbol() {
837        let m = meta("CUtlSymbolLarge", "m_iszName");
838        assert!(matches!(m.decoder, Decoder::String));
839    }
840
841    #[test]
842    fn char_array_is_string() {
843        let m = meta("char[256]", "m_szName");
844        assert!(matches!(m.decoder, Decoder::String));
845        assert!(!m.is_fixed_array());
846    }
847
848    #[test]
849    fn int32_array_is_fixed_array() {
850        let m = meta("int32[4]", "m_values");
851        assert!(m.is_fixed_array());
852        assert_eq!(m.fixed_array_length(), Some(4));
853    }
854
855    #[test]
856    fn dynamic_array_without_serializer() {
857        let m = meta("CNetworkUtlVectorBase< int32 >", "m_items");
858        assert!(m.is_dynamic_array());
859        assert!(!m.is_dynamic_serializer_array());
860    }
861
862    #[test]
863    fn dynamic_serializer_array() {
864        let m = meta_full(
865            "CNetworkUtlVectorBase< SomeType >",
866            "m_items",
867            None,
868            None,
869            None,
870            None,
871            None,
872            true,
873        );
874        assert!(m.is_dynamic_serializer_array());
875    }
876
877    #[test]
878    fn qangle_no_encoder_no_bits() {
879        let m = meta("QAngle", "m_angle");
880        assert!(matches!(m.decoder, Decoder::QAngleCoord));
881    }
882
883    #[test]
884    fn qangle_with_bitcount() {
885        let m = meta_full("QAngle", "m_angle", Some(16), None, None, None, None, false);
886        assert!(matches!(
887            m.decoder,
888            Decoder::QAngleBitCount { bit_count: 16 }
889        ));
890    }
891
892    #[test]
893    fn qangle_pitch_yaw() {
894        let m = meta_full(
895            "QAngle",
896            "m_angle",
897            Some(10),
898            None,
899            None,
900            None,
901            Some("qangle_pitch_yaw"),
902            false,
903        );
904        assert!(matches!(
905            m.decoder,
906            Decoder::QAnglePitchYaw { bit_count: 10 }
907        ));
908    }
909
910    // ── Decoder::decode with BitReader ──
911
912    #[test]
913    fn decode_bool_from_1bit() {
914        let data = [0x01];
915        let mut br = BitReader::new(&data);
916        let mut ctx = FieldDecodeContext::new(1.0 / 64.0);
917        let val = Decoder::Bool.decode(&mut ctx, &mut br).unwrap();
918        assert!(matches!(val, FieldValue::Bool(true)));
919    }
920
921    #[test]
922    fn decode_f32_no_scale() {
923        let bytes = 1.5f32.to_le_bytes();
924        let mut br = BitReader::new(&bytes);
925        let mut ctx = FieldDecodeContext::new(1.0 / 64.0);
926        let val = Decoder::F32NoScale.decode(&mut ctx, &mut br).unwrap();
927        if let FieldValue::F32(f) = val {
928            assert!((f - 1.5).abs() < f32::EPSILON);
929        } else {
930            panic!("expected F32");
931        }
932    }
933
934    #[test]
935    fn decode_string_null_terminated() {
936        let data = b"hello\0";
937        let mut br = BitReader::new(data);
938        let mut ctx = FieldDecodeContext::new(1.0 / 64.0);
939        let val = Decoder::String.decode(&mut ctx, &mut br).unwrap();
940        if let FieldValue::String(s) = val {
941            assert_eq!(&s, b"hello");
942        } else {
943            panic!("expected String");
944        }
945    }
946
947    // ── FieldMetadata helpers ──
948
949    #[test]
950    fn field_metadata_helpers() {
951        let dyn_arr = FieldMetadata {
952            decoder: Decoder::U64,
953            special: Some(FieldSpecialDescriptor::DynamicArray {
954                inner_decoder: Decoder::I64,
955            }),
956        };
957        assert!(dyn_arr.is_dynamic_array());
958        assert!(!dyn_arr.is_fixed_array());
959        assert!(!dyn_arr.is_pointer());
960
961        let fixed = FieldMetadata {
962            decoder: Decoder::I64,
963            special: Some(FieldSpecialDescriptor::FixedArray { length: 8 }),
964        };
965        assert!(fixed.is_fixed_array());
966        assert_eq!(fixed.fixed_array_length(), Some(8));
967
968        let ptr = FieldMetadata {
969            decoder: Decoder::Bool,
970            special: Some(FieldSpecialDescriptor::Pointer),
971        };
972        assert!(ptr.is_pointer());
973    }
974}