Skip to main content

steel_utils/
types.rs

1#![expect(missing_docs, reason = "self-explanatory utility types")]
2
3use std::{
4    borrow::Cow,
5    error::Error,
6    fmt::{self, Debug, Display, Formatter},
7    hash::{Hash, Hasher},
8    io::{self, Cursor, Write},
9    mem::MaybeUninit,
10    str::FromStr,
11};
12
13use bitflags::bitflags;
14use glam::{DVec3, IVec2, IVec3};
15use serde::{Deserialize, Serialize, de::Error as _};
16use simdnbt::owned::{NbtCompound, NbtTag};
17use wincode::{SchemaRead, SchemaWrite, config::Config, io::Reader, io::Writer};
18
19use crate::{
20    axis::Axis,
21    codec::VarInt,
22    direction::Direction,
23    hash::{ComponentHasher, HashComponent},
24    serial::{ReadFrom, WriteTo},
25};
26
27/// A placeholder type for unimplemented component values.
28/// Unlike `()`, this is a distinct type that can have its own trait implementations.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30pub struct Todo;
31
32impl WriteTo for Todo {
33    fn write(&self, _writer: &mut impl Write) -> io::Result<()> {
34        // Placeholder components write nothing
35        Ok(())
36    }
37}
38
39impl ReadFrom for Todo {
40    fn read(_data: &mut Cursor<&[u8]>) -> io::Result<Self> {
41        // Placeholder components read nothing
42        Ok(Todo)
43    }
44}
45
46impl HashComponent for Todo {
47    fn hash_component(&self, hasher: &mut ComponentHasher) {
48        // Hash as empty value
49        hasher.put_empty();
50    }
51}
52
53impl simdnbt::ToNbtTag for Todo {
54    fn to_nbt_tag(self) -> NbtTag {
55        // Placeholder components serialize as empty compound
56        NbtTag::Compound(NbtCompound::new())
57    }
58}
59
60impl simdnbt::FromNbtTag for Todo {
61    fn from_nbt_tag(_tag: simdnbt::borrow::NbtTag) -> Option<Self> {
62        // Placeholder components always deserialize successfully
63        Some(Todo)
64    }
65}
66
67impl HashComponent for Identifier {
68    fn hash_component(&self, hasher: &mut ComponentHasher) {
69        // Identifiers are hashed as strings in "namespace:path" format
70        hasher.put_string(&self.to_string());
71    }
72}
73
74impl simdnbt::ToNbtTag for Identifier {
75    fn to_nbt_tag(self) -> NbtTag {
76        NbtTag::String(self.to_string().into())
77    }
78}
79
80impl simdnbt::FromNbtTag for Identifier {
81    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
82        let s = tag.string()?.to_str();
83        s.parse().ok()
84    }
85}
86
87/// A raw block state id. Using the registry this id can be derived into a block and it's current properties.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
89pub struct BlockStateId(pub u16);
90
91impl WriteTo for BlockStateId {
92    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
93        VarInt(i32::from(self.0)).write(writer)
94    }
95}
96
97impl ReadFrom for BlockStateId {
98    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
99        let id = VarInt::read(data)?.0;
100        #[expect(
101            clippy::cast_sign_loss,
102            reason = "VarInt is validated upstream; block state IDs are non-negative"
103        )]
104        Ok(Self(id as u16))
105    }
106}
107
108/// A chunk position.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct ChunkPos(pub IVec2);
111
112impl Hash for ChunkPos {
113    fn hash<H: Hasher>(&self, state: &mut H) {
114        state.write_u64(PackedChunkPos::from(*self).as_raw() as u64);
115    }
116}
117
118impl ChunkPos {
119    const OFFSETS: [(i32, i32); 8] = [
120        (-1, -1),
121        (0, -1),
122        (1, -1),
123        (-1, 0),
124        (1, 0),
125        (-1, 1),
126        (0, 1),
127        (1, 1),
128    ];
129
130    /// Safety margin in chunks for world generation dependencies.
131    /// Calculated as `(32 + GENERATION_PYRAMID.getStepTo(FULL).accumulatedDependencies().size() + 1) * 2`.
132    /// The accumulated dependencies size for FULL is 9 (radius 8 + 1).
133    const SAFETY_MARGIN_CHUNKS: i32 = (32 + 12 + 1) * 2;
134
135    /// Maximum valid chunk coordinate value.
136    /// Calculated as `SectionPos.blockToSectionCoord(MAX_HORIZONTAL_COORDINATE) - SAFETY_MARGIN_CHUNKS`.
137    pub const MAX_COORDINATE_VALUE: i32 =
138        SectionPos::block_to_section_coord(BlockPos::MAX_HORIZONTAL_COORDINATE)
139            - Self::SAFETY_MARGIN_CHUNKS;
140
141    /// Returns all 8 neighbors of this chunk position.
142    #[must_use]
143    pub fn neighbors(self) -> [ChunkPos; 8] {
144        Self::OFFSETS.map(|(dx, dy)| ChunkPos::new(self.0.x + dx, self.0.y + dy))
145    }
146
147    #[must_use]
148    #[inline]
149    /// Creates a new `ChunkPos` with the given x and y coordinates.
150    pub const fn new(x: i32, y: i32) -> Self {
151        Self(IVec2::new(x, y))
152    }
153
154    /// Creates a `ChunkPos` from a world block position.
155    #[must_use]
156    pub const fn from_block_pos(pos: BlockPos) -> Self {
157        Self::new(
158            SectionPos::block_to_section_coord(pos.0.x),
159            SectionPos::block_to_section_coord(pos.0.z),
160        )
161    }
162
163    /// Creates a `ChunkPos` containing the given floating-point world position.
164    #[must_use]
165    pub fn from_entity_pos(pos: DVec3) -> Self {
166        Self::from_block_pos(BlockPos::from(pos))
167    }
168
169    /// Checks if the given chunk coordinates are within valid bounds.
170    /// Uses `Mth.absMax(x, z) <= MAX_COORDINATE_VALUE`.
171    #[must_use]
172    #[inline]
173    pub const fn is_valid(x: i32, z: i32) -> bool {
174        x.abs().max(z.abs()) <= Self::MAX_COORDINATE_VALUE
175    }
176}
177
178impl WriteTo for ChunkPos {
179    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
180        self.0.write(writer)
181    }
182}
183
184impl ReadFrom for ChunkPos {
185    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
186        Ok(Self(IVec2::read(data)?))
187    }
188}
189
190/// A block position.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
192pub struct BlockPos(pub IVec3);
193
194impl From<DVec3> for BlockPos {
195    fn from(value: DVec3) -> Self {
196        BlockPos(IVec3 {
197            x: value.x.floor() as i32,
198            y: value.y.floor() as i32,
199            z: value.z.floor() as i32,
200        })
201    }
202}
203
204impl BlockPos {
205    pub const ZERO: BlockPos = BlockPos(IVec3::new(0, 0, 0));
206
207    /// Maximum horizontal coordinate value: `(1 << 26) / 2 - 1 = 33554431`
208    pub const MAX_HORIZONTAL_COORDINATE: i32 = (1 << PackedBlockPos::HORIZONTAL_BITS) / 2 - 1;
209
210    /// Creates a new `BlockPos` from coordinates.
211    #[must_use]
212    pub const fn new(x: i32, y: i32, z: i32) -> Self {
213        Self(IVec3::new(x, y, z))
214    }
215
216    /// Returns a new `BlockPos` offset by the given amounts.
217    #[must_use]
218    pub const fn offset(&self, dx: i32, dy: i32, dz: i32) -> Self {
219        Self(IVec3::new(self.0.x + dx, self.0.y + dy, self.0.z + dz))
220    }
221
222    /// Returns the x coordinate.
223    #[must_use]
224    pub const fn x(&self) -> i32 {
225        self.0.x
226    }
227
228    /// Returns the y coordinate.
229    #[must_use]
230    pub const fn y(&self) -> i32 {
231        self.0.y
232    }
233
234    /// Returns the z coordinate.
235    #[must_use]
236    pub const fn z(&self) -> i32 {
237        self.0.z
238    }
239
240    /// Returns the position one block above (Y + 1).
241    #[must_use]
242    pub const fn above(&self) -> Self {
243        self.offset(0, 1, 0)
244    }
245
246    /// Returns the position `n` blocks above (Y + n).
247    #[must_use]
248    pub const fn above_n(&self, n: i32) -> Self {
249        self.offset(0, n, 0)
250    }
251
252    /// Returns the position one block below (Y - 1).
253    #[must_use]
254    pub const fn below(&self) -> Self {
255        self.offset(0, -1, 0)
256    }
257
258    /// Returns the position `n` blocks below (Y - n).
259    #[must_use]
260    pub const fn below_n(&self, n: i32) -> Self {
261        self.offset(0, -n, 0)
262    }
263
264    /// Returns the position one block to the north (Z - 1).
265    #[must_use]
266    pub const fn north(&self) -> Self {
267        self.offset(0, 0, -1)
268    }
269
270    /// Returns the position `n` blocks to the north (Z - n).
271    #[must_use]
272    pub const fn north_n(&self, n: i32) -> Self {
273        self.offset(0, 0, -n)
274    }
275
276    /// Returns the position one block to the south (Z + 1).
277    #[must_use]
278    pub const fn south(&self) -> Self {
279        self.offset(0, 0, 1)
280    }
281
282    /// Returns the position `n` blocks to the south (Z + n).
283    #[must_use]
284    pub const fn south_n(&self, n: i32) -> Self {
285        self.offset(0, 0, n)
286    }
287
288    /// Returns the position one block to the west (X - 1).
289    #[must_use]
290    pub const fn west(&self) -> Self {
291        self.offset(-1, 0, 0)
292    }
293
294    /// Returns the position `n` blocks to the west (X - n).
295    #[must_use]
296    pub const fn west_n(&self, n: i32) -> Self {
297        self.offset(-n, 0, 0)
298    }
299
300    /// Returns the position one block to the east (X + 1).
301    #[must_use]
302    pub const fn east(&self) -> Self {
303        self.offset(1, 0, 0)
304    }
305
306    /// Returns the position `n` blocks to the east (X + n).
307    #[must_use]
308    pub const fn east_n(&self, n: i32) -> Self {
309        self.offset(n, 0, 0)
310    }
311
312    /// Returns the position offset by one block in the given direction.
313    #[must_use]
314    pub const fn relative(self, direction: Direction) -> Self {
315        let (dx, dy, dz) = direction.offset();
316        self.offset(dx, dy, dz)
317    }
318
319    /// Returns the position offset by `n` blocks in the given direction.
320    #[must_use]
321    pub const fn relative_n(&self, direction: Direction, n: i32) -> Self {
322        if n == 0 {
323            *self
324        } else {
325            let (dx, dy, dz) = direction.offset();
326            self.offset(dx * n, dy * n, dz * n)
327        }
328    }
329
330    /// Returns the position offset by `n` blocks along the given axis.
331    #[must_use]
332    pub const fn relative_axis(&self, axis: Axis, n: i32) -> Self {
333        if n == 0 {
334            *self
335        } else {
336            match axis {
337                Axis::X => self.offset(n, 0, 0),
338                Axis::Y => self.offset(0, n, 0),
339                Axis::Z => self.offset(0, 0, n),
340            }
341        }
342    }
343
344    /// Returns a new position with the same X and Z but the given Y.
345    #[must_use]
346    pub const fn at_y(&self, y: i32) -> Self {
347        Self::new(self.0.x, y, self.0.z)
348    }
349
350    /// Returns a new position with all coordinates multiplied by the given factor.
351    #[must_use]
352    pub const fn multiply(&self, factor: i32) -> Self {
353        if factor == 1 {
354            *self
355        } else if factor == 0 {
356            Self::ZERO
357        } else {
358            Self::new(self.0.x * factor, self.0.y * factor, self.0.z * factor)
359        }
360    }
361
362    /// Returns the center of this block as a floating-point position.
363    #[must_use]
364    pub fn get_center(&self) -> (f64, f64, f64) {
365        (
366            f64::from(self.0.x) + 0.5,
367            f64::from(self.0.y) + 0.5,
368            f64::from(self.0.z) + 0.5,
369        )
370    }
371
372    /// Returns the bottom center of this block (center of the bottom face).
373    #[must_use]
374    pub fn get_bottom_center(&self) -> (f64, f64, f64) {
375        (
376            f64::from(self.0.x) + 0.5,
377            f64::from(self.0.y),
378            f64::from(self.0.z) + 0.5,
379        )
380    }
381
382    /// Creates a `BlockPos` containing the given floating-point coordinates.
383    #[must_use]
384    pub const fn containing(x: f64, y: f64, z: f64) -> Self {
385        Self::new(x.floor() as i32, y.floor() as i32, z.floor() as i32)
386    }
387
388    /// Returns the minimum coordinates of two positions.
389    #[must_use]
390    pub const fn min(a: BlockPos, b: BlockPos) -> Self {
391        Self::new(a.0.x.min(b.0.x), a.0.y.min(b.0.y), a.0.z.min(b.0.z))
392    }
393
394    /// Returns the maximum coordinates of two positions.
395    #[must_use]
396    pub const fn max(a: BlockPos, b: BlockPos) -> Self {
397        Self::new(a.0.x.max(b.0.x), a.0.y.max(b.0.y), a.0.z.max(b.0.z))
398    }
399}
400
401impl ReadFrom for BlockPos {
402    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
403        let packed = <i64 as ReadFrom>::read(data)?;
404        Ok(PackedBlockPos::from_raw(packed).into())
405    }
406}
407
408/// A chunk section position (16x16x16 region).
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
410pub struct SectionPos(pub IVec3);
411
412impl SectionPos {
413    const SECTION_BITS: i32 = 4;
414    const SECTION_SIZE: i32 = 1 << Self::SECTION_BITS; // 16
415    const SECTION_MASK: i32 = Self::SECTION_SIZE - 1; // 15
416
417    /// Creates a new `SectionPos` from section coordinates.
418    #[must_use]
419    pub const fn new(x: i32, y: i32, z: i32) -> Self {
420        Self(IVec3::new(x, y, z))
421    }
422
423    /// Converts a block coordinate to a section coordinate.
424    #[must_use]
425    #[inline]
426    pub const fn block_to_section_coord(block_coord: i32) -> i32 {
427        block_coord >> Self::SECTION_BITS
428    }
429
430    /// Creates a `SectionPos` from a `BlockPos`.
431    #[must_use]
432    pub const fn from_block_pos(pos: BlockPos) -> Self {
433        Self::new(
434            Self::block_to_section_coord(pos.0.x),
435            Self::block_to_section_coord(pos.0.y),
436            Self::block_to_section_coord(pos.0.z),
437        )
438    }
439
440    /// Creates a `SectionPos` containing the given floating-point world position.
441    #[must_use]
442    pub fn from_entity_pos(pos: DVec3) -> Self {
443        Self::from_block_pos(BlockPos::from(pos))
444    }
445
446    /// Gets the X coordinate.
447    #[must_use]
448    pub const fn x(&self) -> i32 {
449        self.0.x
450    }
451
452    /// Gets the Y coordinate.
453    #[must_use]
454    pub const fn y(&self) -> i32 {
455        self.0.y
456    }
457
458    /// Gets the Z coordinate.
459    #[must_use]
460    pub const fn z(&self) -> i32 {
461        self.0.z
462    }
463
464    /// Converts section-relative coordinates to an absolute block X coordinate.
465    #[must_use]
466    pub const fn relative_to_block_x(&self, relative: PackedSectionBlockPos) -> i32 {
467        (self.0.x << Self::SECTION_BITS) + relative.x() as i32
468    }
469
470    /// Converts section-relative coordinates to an absolute block Y coordinate.
471    #[must_use]
472    pub const fn relative_to_block_y(&self, relative: PackedSectionBlockPos) -> i32 {
473        (self.0.y << Self::SECTION_BITS) + relative.y() as i32
474    }
475
476    /// Converts section-relative coordinates to an absolute block Z coordinate.
477    #[must_use]
478    pub const fn relative_to_block_z(&self, relative: PackedSectionBlockPos) -> i32 {
479        (self.0.z << Self::SECTION_BITS) + relative.z() as i32
480    }
481
482    /// Packs a block position into a section-relative offset.
483    /// Format: (x << 8) | (z << 4) | y (each coordinate masked to 4 bits)
484    #[must_use]
485    #[inline]
486    pub const fn section_relative_pos(pos: BlockPos) -> PackedSectionBlockPos {
487        PackedSectionBlockPos::from_block_pos(pos)
488    }
489
490    /// Converts a section-relative packed position back to a block position.
491    #[must_use]
492    pub const fn relative_to_block_pos(&self, relative: PackedSectionBlockPos) -> BlockPos {
493        BlockPos(IVec3::new(
494            self.relative_to_block_x(relative),
495            self.relative_to_block_y(relative),
496            self.relative_to_block_z(relative),
497        ))
498    }
499}
500
501/// A chunk position in Steel's packed `i64` layout.
502#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
503pub struct PackedChunkPos(i64);
504
505impl PackedChunkPos {
506    /// Creates a packed chunk position from its raw representation.
507    #[must_use]
508    pub const fn from_raw(raw: i64) -> Self {
509        Self(raw)
510    }
511
512    /// Returns the raw packed representation.
513    #[must_use]
514    pub const fn as_raw(self) -> i64 {
515        self.0
516    }
517
518    /// Converts this packed value into a `ChunkPos`.
519    #[must_use]
520    pub const fn to_chunk_pos(self) -> ChunkPos {
521        ChunkPos(IVec2::new(
522            (self.0 & 0xFFFF_FFFF) as i32,
523            (self.0 >> 32) as i32,
524        ))
525    }
526}
527
528impl From<ChunkPos> for PackedChunkPos {
529    fn from(pos: ChunkPos) -> Self {
530        Self((i64::from(pos.0.x) & 0xFFFF_FFFF) | ((i64::from(pos.0.y) & 0xFFFF_FFFF) << 32))
531    }
532}
533
534impl From<PackedChunkPos> for ChunkPos {
535    fn from(pos: PackedChunkPos) -> Self {
536        pos.to_chunk_pos()
537    }
538}
539
540impl ReadFrom for PackedChunkPos {
541    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
542        Ok(Self::from_raw(<i64 as ReadFrom>::read(data)?))
543    }
544}
545
546impl WriteTo for PackedChunkPos {
547    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
548        self.0.write(writer)
549    }
550}
551
552/// A block position in Minecraft's packed protocol `i64` layout.
553#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
554pub struct PackedBlockPos(i64);
555
556impl PackedBlockPos {
557    const HORIZONTAL_BITS: u32 = 26;
558    const Y_BITS: u32 = 12;
559    const X_OFFSET: u32 = Self::HORIZONTAL_BITS + Self::Y_BITS;
560    const Z_OFFSET: u32 = Self::Y_BITS;
561    const XZ_MASK: i64 = (1i64 << Self::HORIZONTAL_BITS) - 1;
562    const Y_MASK: i64 = (1i64 << Self::Y_BITS) - 1;
563
564    /// Creates a packed block position from its raw representation.
565    #[must_use]
566    pub const fn from_raw(raw: i64) -> Self {
567        Self(raw)
568    }
569
570    /// Returns the raw packed representation.
571    #[must_use]
572    pub const fn as_raw(self) -> i64 {
573        self.0
574    }
575
576    /// Converts this packed value into a `BlockPos`.
577    #[must_use]
578    pub const fn to_block_pos(self) -> BlockPos {
579        let x = self.0 >> Self::X_OFFSET;
580        let y = self.0 & Self::Y_MASK;
581        let z = (self.0 >> Self::Z_OFFSET) & Self::XZ_MASK;
582
583        let x = (x << (64 - Self::HORIZONTAL_BITS)) >> (64 - Self::HORIZONTAL_BITS);
584        let y = (y << (64 - Self::Y_BITS)) >> (64 - Self::Y_BITS);
585        let z = (z << (64 - Self::HORIZONTAL_BITS)) >> (64 - Self::HORIZONTAL_BITS);
586
587        BlockPos(IVec3::new(x as i32, y as i32, z as i32))
588    }
589}
590
591impl From<BlockPos> for PackedBlockPos {
592    fn from(pos: BlockPos) -> Self {
593        let x = i64::from(pos.0.x);
594        let y = i64::from(pos.0.y);
595        let z = i64::from(pos.0.z);
596        Self(
597            ((x & Self::XZ_MASK) << Self::X_OFFSET)
598                | ((z & Self::XZ_MASK) << Self::Z_OFFSET)
599                | (y & Self::Y_MASK),
600        )
601    }
602}
603
604impl From<PackedBlockPos> for BlockPos {
605    fn from(pos: PackedBlockPos) -> Self {
606        pos.to_block_pos()
607    }
608}
609
610impl ReadFrom for PackedBlockPos {
611    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
612        Ok(Self::from_raw(<i64 as ReadFrom>::read(data)?))
613    }
614}
615
616impl WriteTo for PackedBlockPos {
617    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
618        self.0.write(writer)
619    }
620}
621
622/// A section position in Minecraft's packed `i64` layout.
623#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
624pub struct PackedSectionPos(i64);
625
626impl PackedSectionPos {
627    const XZ_BITS: u32 = 22;
628    const Y_BITS: u32 = 20;
629    const X_OFFSET: u32 = Self::XZ_BITS + Self::Y_BITS;
630    const Z_OFFSET: u32 = Self::Y_BITS;
631    const XZ_MASK: i64 = (1i64 << Self::XZ_BITS) - 1;
632    const Y_MASK: i64 = (1i64 << Self::Y_BITS) - 1;
633
634    /// Creates a packed section position from its raw representation.
635    #[must_use]
636    pub const fn from_raw(raw: i64) -> Self {
637        Self(raw)
638    }
639
640    /// Returns the raw packed representation.
641    #[must_use]
642    pub const fn as_raw(self) -> i64 {
643        self.0
644    }
645
646    /// Converts this packed value into a `SectionPos`.
647    #[must_use]
648    pub const fn to_section_pos(self) -> SectionPos {
649        let x = self.0 >> Self::X_OFFSET;
650        let z = (self.0 >> Self::Z_OFFSET) & Self::XZ_MASK;
651        let y = self.0 & Self::Y_MASK;
652
653        let x = (x << (64 - Self::XZ_BITS)) >> (64 - Self::XZ_BITS);
654        let y = (y << (64 - Self::Y_BITS)) >> (64 - Self::Y_BITS);
655        let z = (z << (64 - Self::XZ_BITS)) >> (64 - Self::XZ_BITS);
656
657        SectionPos(IVec3::new(x as i32, y as i32, z as i32))
658    }
659}
660
661impl From<SectionPos> for PackedSectionPos {
662    fn from(pos: SectionPos) -> Self {
663        let x = i64::from(pos.0.x);
664        let y = i64::from(pos.0.y);
665        let z = i64::from(pos.0.z);
666        Self(
667            ((x & Self::XZ_MASK) << Self::X_OFFSET)
668                | ((z & Self::XZ_MASK) << Self::Z_OFFSET)
669                | (y & Self::Y_MASK),
670        )
671    }
672}
673
674impl From<PackedSectionPos> for SectionPos {
675    fn from(pos: PackedSectionPos) -> Self {
676        pos.to_section_pos()
677    }
678}
679
680impl ReadFrom for PackedSectionPos {
681    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
682        Ok(Self::from_raw(<i64 as ReadFrom>::read(data)?))
683    }
684}
685
686impl WriteTo for PackedSectionPos {
687    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
688        self.0.write(writer)
689    }
690}
691
692/// A block's X/Z position packed relative to its containing chunk.
693///
694/// Layout: `(x << 4) | z`, with each coordinate using 4 bits.
695#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
696pub struct PackedChunkLocalXZ(u8);
697
698impl PackedChunkLocalXZ {
699    const COORD_MASK: u8 = 0x0f;
700
701    /// Packs an absolute block position by masking X and Z to chunk-local range.
702    #[must_use]
703    pub const fn from_block_pos(pos: BlockPos) -> Self {
704        Self::from_local_unchecked(
705            (pos.0.x & SectionPos::SECTION_MASK) as u8,
706            (pos.0.z & SectionPos::SECTION_MASK) as u8,
707        )
708    }
709
710    /// Packs validated chunk-local X/Z coordinates.
711    #[must_use]
712    pub const fn from_local_xz(x: u8, z: u8) -> Option<Self> {
713        if x < 16 && z < 16 {
714            Some(Self::from_local_unchecked(x, z))
715        } else {
716            None
717        }
718    }
719
720    /// Rebuilds a packed chunk-local X/Z position from its raw representation.
721    #[must_use]
722    pub const fn from_raw(raw: u8) -> Self {
723        Self(raw)
724    }
725
726    /// Returns the raw packed representation.
727    #[must_use]
728    pub const fn as_u8(self) -> u8 {
729        self.0
730    }
731
732    /// Returns the chunk-local X coordinate.
733    #[must_use]
734    pub const fn x(self) -> u8 {
735        (self.0 >> 4) & Self::COORD_MASK
736    }
737
738    /// Returns the chunk-local Z coordinate.
739    #[must_use]
740    pub const fn z(self) -> u8 {
741        self.0 & Self::COORD_MASK
742    }
743
744    const fn from_local_unchecked(x: u8, z: u8) -> Self {
745        Self((x << 4) | z)
746    }
747}
748
749impl From<BlockPos> for PackedChunkLocalXZ {
750    fn from(pos: BlockPos) -> Self {
751        Self::from_block_pos(pos)
752    }
753}
754
755impl ReadFrom for PackedChunkLocalXZ {
756    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
757        Ok(Self::from_raw(<u8 as ReadFrom>::read(data)?))
758    }
759}
760
761impl WriteTo for PackedChunkLocalXZ {
762    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
763        self.0.write(writer)
764    }
765}
766
767/// A block position packed relative to its containing 16x16x16 section.
768///
769/// Layout: `(x << 8) | (z << 4) | y`, with each coordinate using 4 bits.
770#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
771pub struct PackedSectionBlockPos(u16);
772
773impl PackedSectionBlockPos {
774    const COORD_MASK: u16 = 0x0f;
775    const RAW_MASK: u16 = 0x0fff;
776
777    /// Packs an absolute block position by masking each coordinate to section-local range.
778    #[must_use]
779    #[inline]
780    pub const fn from_block_pos(pos: BlockPos) -> Self {
781        Self::from_local_unchecked(
782            (pos.0.x & SectionPos::SECTION_MASK) as u8,
783            (pos.0.y & SectionPos::SECTION_MASK) as u8,
784            (pos.0.z & SectionPos::SECTION_MASK) as u8,
785        )
786    }
787
788    /// Packs validated section-local coordinates.
789    #[must_use]
790    pub const fn from_local_xyz(x: u8, y: u8, z: u8) -> Option<Self> {
791        if x < 16 && y < 16 && z < 16 {
792            Some(Self::from_local_unchecked(x, y, z))
793        } else {
794            None
795        }
796    }
797
798    /// Rebuilds a packed section block position from its raw representation.
799    #[must_use]
800    pub const fn from_raw(raw: u16) -> Option<Self> {
801        if raw & !Self::RAW_MASK == 0 {
802            Some(Self(raw))
803        } else {
804            None
805        }
806    }
807
808    /// Returns the raw packed representation.
809    #[must_use]
810    pub const fn as_u16(self) -> u16 {
811        self.0
812    }
813
814    /// Returns the section-local X coordinate.
815    #[must_use]
816    pub const fn x(self) -> u8 {
817        ((self.0 >> 8) & Self::COORD_MASK) as u8
818    }
819
820    /// Returns the section-local Y coordinate.
821    #[must_use]
822    pub const fn y(self) -> u8 {
823        (self.0 & Self::COORD_MASK) as u8
824    }
825
826    /// Returns the section-local Z coordinate.
827    #[must_use]
828    pub const fn z(self) -> u8 {
829        ((self.0 >> 4) & Self::COORD_MASK) as u8
830    }
831
832    /// Converts this section-relative position to an absolute block position.
833    #[must_use]
834    pub const fn to_block_pos(self, section_pos: SectionPos) -> BlockPos {
835        section_pos.relative_to_block_pos(self)
836    }
837
838    const fn from_local_unchecked(x: u8, y: u8, z: u8) -> Self {
839        Self(((x as u16) << 8) | ((z as u16) << 4) | y as u16)
840    }
841}
842
843impl From<BlockPos> for PackedSectionBlockPos {
844    fn from(pos: BlockPos) -> Self {
845        Self::from_block_pos(pos)
846    }
847}
848
849impl TryFrom<u16> for PackedSectionBlockPos {
850    type Error = InvalidPackedSectionBlockPos;
851
852    fn try_from(raw: u16) -> Result<Self, Self::Error> {
853        Self::from_raw(raw).ok_or(InvalidPackedSectionBlockPos { raw })
854    }
855}
856
857/// Error returned when a raw section-relative block position uses reserved bits.
858#[derive(Debug, Clone, Copy, PartialEq, Eq)]
859pub struct InvalidPackedSectionBlockPos {
860    raw: u16,
861}
862
863impl InvalidPackedSectionBlockPos {
864    /// Returns the invalid raw value.
865    #[must_use]
866    pub const fn raw(self) -> u16 {
867        self.raw
868    }
869}
870
871impl Display for InvalidPackedSectionBlockPos {
872    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
873        write!(
874            f,
875            "packed section block position {:#06x} uses reserved bits",
876            self.raw
877        )
878    }
879}
880
881impl Error for InvalidPackedSectionBlockPos {}
882
883impl ReadFrom for SectionPos {
884    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
885        Ok(<PackedSectionPos as ReadFrom>::read(data)?.into())
886    }
887}
888
889impl WriteTo for SectionPos {
890    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
891        PackedSectionPos::from(*self).write(writer)
892    }
893}
894
895/// An integer axis-aligned bounding box for structure pieces.
896///
897/// Corresponds to vanilla's `BoundingBox` (not the float `AABB`/`AABBd`
898/// for block shapes or entity collision). Coordinates are absolute world block positions.
899#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SchemaWrite, SchemaRead)]
900pub struct BoundingBox {
901    /// Minimum X coordinate (inclusive).
902    pub min_x: i32,
903    /// Minimum Y coordinate (inclusive).
904    pub min_y: i32,
905    /// Minimum Z coordinate (inclusive).
906    pub min_z: i32,
907    /// Maximum X coordinate (inclusive).
908    pub max_x: i32,
909    /// Maximum Y coordinate (inclusive).
910    pub max_y: i32,
911    /// Maximum Z coordinate (inclusive).
912    pub max_z: i32,
913}
914
915impl BoundingBox {
916    /// Creates a new bounding box, normalizing so min <= max on each axis.
917    #[must_use]
918    pub const fn new(x1: i32, y1: i32, z1: i32, x2: i32, y2: i32, z2: i32) -> Self {
919        Self {
920            min_x: x1.min(x2),
921            min_y: y1.min(y2),
922            min_z: z1.min(z2),
923            max_x: x1.max(x2),
924            max_y: y1.max(y2),
925            max_z: z1.max(z2),
926        }
927    }
928
929    /// Creates a bounding box from two corner block positions.
930    #[must_use]
931    pub const fn from_corners(a: BlockPos, b: BlockPos) -> Self {
932        Self::new(a.0.x, a.0.y, a.0.z, b.0.x, b.0.y, b.0.z)
933    }
934
935    /// Returns whether this bounding box intersects another.
936    #[must_use]
937    pub const fn intersects(&self, other: &Self) -> bool {
938        self.max_x >= other.min_x
939            && self.min_x <= other.max_x
940            && self.max_z >= other.min_z
941            && self.min_z <= other.max_z
942            && self.max_y >= other.min_y
943            && self.min_y <= other.max_y
944    }
945
946    /// Returns whether this bounding box intersects the given XZ range.
947    #[must_use]
948    pub const fn intersects_xz(&self, min_x: i32, min_z: i32, max_x: i32, max_z: i32) -> bool {
949        self.max_x >= min_x && self.min_x <= max_x && self.max_z >= min_z && self.min_z <= max_z
950    }
951
952    /// Returns whether the given block position is inside this bounding box.
953    #[must_use]
954    pub const fn is_inside(&self, pos: BlockPos) -> bool {
955        self.contains_xyz(pos.0.x, pos.0.y, pos.0.z)
956    }
957
958    /// Returns whether the given coordinates are inside this bounding box.
959    #[must_use]
960    pub const fn contains_xyz(&self, x: i32, y: i32, z: i32) -> bool {
961        x >= self.min_x
962            && x <= self.max_x
963            && z >= self.min_z
964            && z <= self.max_z
965            && y >= self.min_y
966            && y <= self.max_y
967    }
968
969    /// Returns the center of this bounding box.
970    #[must_use]
971    pub const fn get_center(&self) -> BlockPos {
972        BlockPos(IVec3::new(
973            self.min_x + (self.max_x - self.min_x + 1) / 2,
974            self.min_y + (self.max_y - self.min_y + 1) / 2,
975            self.min_z + (self.max_z - self.min_z + 1) / 2,
976        ))
977    }
978
979    /// Returns the span (size) along the X axis.
980    #[must_use]
981    pub const fn get_x_span(&self) -> i32 {
982        self.max_x - self.min_x + 1
983    }
984
985    /// Returns the span (size) along the Y axis.
986    #[must_use]
987    pub const fn get_y_span(&self) -> i32 {
988        self.max_y - self.min_y + 1
989    }
990
991    /// Returns the span (size) along the Z axis.
992    #[must_use]
993    pub const fn get_z_span(&self) -> i32 {
994        self.max_z - self.min_z + 1
995    }
996
997    /// Returns the smallest bounding box that contains both `a` and `b`.
998    #[must_use]
999    pub const fn encapsulating(a: &Self, b: &Self) -> Self {
1000        Self {
1001            min_x: a.min_x.min(b.min_x),
1002            min_y: a.min_y.min(b.min_y),
1003            min_z: a.min_z.min(b.min_z),
1004            max_x: a.max_x.max(b.max_x),
1005            max_y: a.max_y.max(b.max_y),
1006            max_z: a.max_z.max(b.max_z),
1007        }
1008    }
1009
1010    /// Returns a new bounding box moved by the given offset.
1011    #[must_use]
1012    pub const fn moved(&self, dx: i32, dy: i32, dz: i32) -> Self {
1013        Self {
1014            min_x: self.min_x + dx,
1015            min_y: self.min_y + dy,
1016            min_z: self.min_z + dz,
1017            max_x: self.max_x + dx,
1018            max_y: self.max_y + dy,
1019            max_z: self.max_z + dz,
1020        }
1021    }
1022
1023    /// Returns a new bounding box inflated by the given amounts on each axis.
1024    #[must_use]
1025    pub const fn inflated_by(&self, x: i32, y: i32, z: i32) -> Self {
1026        Self {
1027            min_x: self.min_x - x,
1028            min_y: self.min_y - y,
1029            min_z: self.min_z - z,
1030            max_x: self.max_x + x,
1031            max_y: self.max_y + y,
1032            max_z: self.max_z + z,
1033        }
1034    }
1035}
1036
1037/// The game type.
1038#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1039#[expect(missing_docs, reason = "variant names are self-explanatory")]
1040pub enum GameType {
1041    Survival = 0,
1042    Creative = 1,
1043    Adventure = 2,
1044    Spectator = 3,
1045}
1046
1047impl GameType {
1048    /// Returns the name of the game type.
1049    #[must_use]
1050    pub const fn name(self) -> &'static str {
1051        match self {
1052            GameType::Survival => "survival",
1053            GameType::Creative => "creative",
1054            GameType::Adventure => "adventure",
1055            GameType::Spectator => "spectator",
1056        }
1057    }
1058}
1059
1060impl ReadFrom for GameType {
1061    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
1062        let value = VarInt::read(data)?.0;
1063        match value {
1064            0 => Ok(GameType::Survival),
1065            1 => Ok(GameType::Creative),
1066            2 => Ok(GameType::Adventure),
1067            3 => Ok(GameType::Spectator),
1068            _ => Err(io::Error::new(
1069                io::ErrorKind::InvalidData,
1070                "Invalid GameType",
1071            )),
1072        }
1073    }
1074}
1075
1076impl From<GameType> for i8 {
1077    fn from(value: GameType) -> Self {
1078        value as i8
1079    }
1080}
1081
1082impl From<GameType> for i32 {
1083    fn from(value: GameType) -> Self {
1084        value as i32
1085    }
1086}
1087
1088impl From<GameType> for f32 {
1089    fn from(value: GameType) -> Self {
1090        f32::from(value as i8)
1091    }
1092}
1093
1094impl From<i8> for GameType {
1095    fn from(value: i8) -> Self {
1096        match value {
1097            1 => GameType::Creative,
1098            2 => GameType::Adventure,
1099            3 => GameType::Spectator,
1100            _ => GameType::Survival,
1101        }
1102    }
1103}
1104
1105impl From<i32> for GameType {
1106    fn from(value: i32) -> Self {
1107        match value {
1108            1 => GameType::Creative,
1109            2 => GameType::Adventure,
1110            3 => GameType::Spectator,
1111            _ => GameType::Survival,
1112        }
1113    }
1114}
1115
1116impl From<f32> for GameType {
1117    fn from(value: f32) -> Self {
1118        match value {
1119            1. => GameType::Creative,
1120            2. => GameType::Adventure,
1121            3. => GameType::Spectator,
1122            _ => GameType::Survival,
1123        }
1124    }
1125}
1126
1127/// World difficulty level.
1128///
1129/// Controls starvation damage thresholds, mob spawning behavior,
1130/// and various other gameplay tweaks.
1131#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1132#[repr(u8)]
1133pub enum Difficulty {
1134    /// No hostile mobs, no starvation, health regenerates quickly.
1135    Peaceful = 0,
1136    /// Hostile mobs deal less damage, starvation stops at 10 HP.
1137    Easy = 1,
1138    /// Default difficulty, starvation stops at 1 HP.
1139    #[default]
1140    Normal = 2,
1141    /// Hostile mobs deal more damage, starvation can kill.
1142    Hard = 3,
1143}
1144
1145#[expect(clippy::match_same_arms, reason = "cause it looks better")]
1146impl From<u8> for Difficulty {
1147    fn from(value: u8) -> Self {
1148        match value {
1149            0 => Difficulty::Peaceful,
1150            1 => Difficulty::Easy,
1151            2 => Difficulty::Normal,
1152            3 => Difficulty::Hard,
1153            _ => Difficulty::Normal,
1154        }
1155    }
1156}
1157
1158impl From<Difficulty> for u8 {
1159    fn from(value: Difficulty) -> Self {
1160        value as u8
1161    }
1162}
1163
1164impl ReadFrom for Difficulty {
1165    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
1166        let value = <u8 as ReadFrom>::read(data)?;
1167        match value {
1168            0 => Ok(Difficulty::Peaceful),
1169            1 => Ok(Difficulty::Easy),
1170            2 => Ok(Difficulty::Normal),
1171            3 => Ok(Difficulty::Hard),
1172            _ => Err(io::Error::new(
1173                io::ErrorKind::InvalidData,
1174                format!("Invalid Difficulty: {value}"),
1175            )),
1176        }
1177    }
1178}
1179
1180impl WriteTo for Difficulty {
1181    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
1182        (*self as u8).write(writer)
1183    }
1184}
1185
1186impl Serialize for Difficulty {
1187    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1188    where
1189        S: serde::Serializer,
1190    {
1191        serializer.serialize_u8(*self as u8)
1192    }
1193}
1194
1195impl<'de> Deserialize<'de> for Difficulty {
1196    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1197    where
1198        D: serde::Deserializer<'de>,
1199    {
1200        let id = u8::deserialize(deserializer)?;
1201        Ok(Self::from(id))
1202    }
1203}
1204
1205/// An identifier used by Minecraft.
1206#[derive(Clone, PartialEq, Eq, Hash, Default)]
1207pub struct Identifier {
1208    /// The namespace of the identifier.
1209    pub namespace: Cow<'static, str>,
1210    /// The path of the identifier.
1211    pub path: Cow<'static, str>,
1212}
1213
1214impl Debug for Identifier {
1215    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1216        f.write_str(&format!("{}:{}", self.namespace, self.path))
1217    }
1218}
1219
1220impl Identifier {
1221    /// The vanilla namespace.
1222    pub const VANILLA_NAMESPACE: &'static str = "minecraft";
1223
1224    /// Creates a new `Identifier` with the given namespace and path.
1225    #[must_use]
1226    pub fn new(
1227        namespace: impl Into<Cow<'static, str>>,
1228        path: impl Into<Cow<'static, str>>,
1229    ) -> Self {
1230        Identifier {
1231            namespace: namespace.into(),
1232            path: path.into(),
1233        }
1234    }
1235    #[must_use]
1236    pub const fn new_static(namespace: &'static str, path: &'static str) -> Self {
1237        Identifier {
1238            namespace: Cow::Borrowed(namespace),
1239            path: Cow::Borrowed(path),
1240        }
1241    }
1242
1243    /// Creates a new `Identifier` with the vanilla namespace.
1244    #[must_use]
1245    pub const fn vanilla(path: String) -> Self {
1246        Identifier {
1247            namespace: Cow::Borrowed(Self::VANILLA_NAMESPACE),
1248            path: Cow::Owned(path),
1249        }
1250    }
1251
1252    /// Creates a new `Identifier` with the vanilla namespace and a static path.
1253    #[must_use]
1254    pub const fn vanilla_static(path: &'static str) -> Self {
1255        Identifier {
1256            namespace: Cow::Borrowed(Self::VANILLA_NAMESPACE),
1257            path: Cow::Borrowed(path),
1258        }
1259    }
1260
1261    /// Returns whether the character is a valid namespace character.
1262    #[must_use]
1263    pub const fn valid_namespace_char(char: char) -> bool {
1264        char == '_'
1265            || char == '-'
1266            || char.is_ascii_lowercase()
1267            || char.is_ascii_digit()
1268            || char == '.'
1269    }
1270
1271    /// Returns whether the character is a valid path character.
1272    #[must_use]
1273    pub const fn valid_char(char: char) -> bool {
1274        Self::valid_namespace_char(char) || char == '/'
1275    }
1276
1277    /// Returns whether the namespace is valid.
1278    pub fn validate_namespace(namespace: &str) -> bool {
1279        namespace.chars().all(Self::valid_namespace_char)
1280    }
1281
1282    /// Returns whether the path is valid.
1283    pub fn validate_path(path: &str) -> bool {
1284        path.chars().all(Self::valid_char)
1285    }
1286
1287    /// Returns whether the namespace and path are valid.
1288    #[must_use]
1289    pub fn validate(namespace: &str, path: &str) -> bool {
1290        Self::validate_namespace(namespace) && Self::validate_path(path)
1291    }
1292}
1293
1294impl Display for Identifier {
1295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1296        write!(f, "{}:{}", self.namespace, self.path)
1297    }
1298}
1299
1300impl FromStr for Identifier {
1301    type Err = &'static str;
1302
1303    fn from_str(s: &str) -> Result<Self, Self::Err> {
1304        let parts: Vec<&str> = s.split(':').collect();
1305        if parts.len() != 2 {
1306            return Err("Invalid resource location");
1307        }
1308
1309        if !Identifier::validate_namespace(parts[0]) {
1310            return Err("Invalid namespace");
1311        }
1312
1313        if !Identifier::validate_path(parts[1]) {
1314            return Err("Invalid path");
1315        }
1316
1317        Ok(Identifier {
1318            namespace: Cow::Owned(parts[0].to_string()),
1319            path: Cow::Owned(parts[1].to_string()),
1320        })
1321    }
1322}
1323impl Serialize for Identifier {
1324    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1325    where
1326        S: serde::Serializer,
1327    {
1328        serializer.serialize_str(&self.to_string())
1329    }
1330}
1331
1332impl<'de> Deserialize<'de> for Identifier {
1333    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1334    where
1335        D: serde::Deserializer<'de>,
1336    {
1337        let s = String::deserialize(deserializer)?;
1338        Identifier::from_str(&s).map_err(D::Error::custom)
1339    }
1340}
1341
1342// SAFETY: This implementation delegates to the `str` and `String` implementations
1343// which are already safe, and the Identifier type has the same serialized representation
1344// as a String (length-prefixed UTF-8 bytes). The size_of method returns exactly the
1345// number of bytes that write will produce.
1346unsafe impl<C: Config> SchemaWrite<C> for Identifier {
1347    type Src = Identifier;
1348
1349    fn size_of(src: &Self::Src) -> wincode::WriteResult<usize> {
1350        <str as SchemaWrite<C>>::size_of(&src.to_string())
1351    }
1352
1353    fn write(writer: impl Writer, src: &Self::Src) -> wincode::WriteResult<()> {
1354        <str as SchemaWrite<C>>::write(writer, &src.to_string())
1355    }
1356}
1357
1358// SAFETY: This implementation delegates to the `String` implementation which is
1359// already safe, and then validates the result as a valid Identifier. The read
1360// method initializes `dst` if and only if it returns Ok(()).
1361unsafe impl<'de, C: Config> SchemaRead<'de, C> for Identifier {
1362    type Dst = Identifier;
1363
1364    fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> wincode::ReadResult<()> {
1365        let mut s = MaybeUninit::<String>::uninit();
1366        <String as SchemaRead<'de, C>>::read(reader, &mut s)?;
1367
1368        // SAFETY: String::read succeeded, so s is initialized
1369        let s = unsafe { s.assume_init() };
1370
1371        dst.write(Identifier::from_str(&s).map_err(wincode::ReadError::Custom)?);
1372        Ok(())
1373    }
1374}
1375
1376/// Represents the hand used for an interaction.
1377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1378pub enum InteractionHand {
1379    /// The main hand.
1380    MainHand,
1381    /// The off hand.
1382    OffHand,
1383}
1384
1385impl ReadFrom for InteractionHand {
1386    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
1387        let id = VarInt::read(data)?.0;
1388        match id {
1389            0 => Ok(InteractionHand::MainHand),
1390            1 => Ok(InteractionHand::OffHand),
1391            _ => Err(io::Error::other("Invalid InteractionHand id")),
1392        }
1393    }
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398    use super::*;
1399
1400    #[test]
1401    fn test_block_pos_roundtrip() {
1402        let positions = vec![
1403            BlockPos(IVec3::new(0, -61, -2)),
1404            BlockPos(IVec3::new(0, 0, 0)),
1405            BlockPos(IVec3::new(100, 64, -100)),
1406            BlockPos(IVec3::new(-1000, -64, 1000)),
1407            BlockPos(IVec3::new(33_554_431, 2047, 33_554_431)), // Max positive values
1408            BlockPos(IVec3::new(-33_554_432, -2048, -33_554_432)), // Max negative values
1409        ];
1410
1411        for pos in positions {
1412            let encoded = PackedBlockPos::from(pos);
1413            let decoded = encoded.to_block_pos();
1414            assert_eq!(
1415                pos, decoded,
1416                "Roundtrip failed for {pos:?}: encoded={encoded:?}, decoded={decoded:?}"
1417            );
1418        }
1419    }
1420
1421    #[test]
1422    fn test_block_pos_specific_case() {
1423        // Test the specific case from the bug report
1424        let pos = BlockPos(IVec3::new(0, -61, -2));
1425        let encoded = PackedBlockPos::from(pos);
1426        let decoded = encoded.to_block_pos();
1427        assert_eq!(pos, decoded, "Position 0, -61, -2 failed roundtrip");
1428    }
1429
1430    #[test]
1431    fn packed_chunk_local_xz_masks_absolute_coordinates() {
1432        let packed = PackedChunkLocalXZ::from_block_pos(BlockPos::new(17, 64, 18));
1433
1434        assert_eq!(packed.as_u8(), 0x12);
1435        assert_eq!(packed.x(), 1);
1436        assert_eq!(packed.z(), 2);
1437    }
1438
1439    #[test]
1440    fn packed_chunk_local_xz_rejects_invalid_local_coordinates() {
1441        assert!(PackedChunkLocalXZ::from_local_xz(15, 15).is_some());
1442        assert!(PackedChunkLocalXZ::from_local_xz(16, 0).is_none());
1443        assert!(PackedChunkLocalXZ::from_local_xz(0, 16).is_none());
1444    }
1445
1446    #[test]
1447    fn entity_positions_floor_before_chunk_and_section_conversion() {
1448        let pos = DVec3::new(-4352.5, -16.5, -4405.5);
1449
1450        assert_eq!(BlockPos::from(pos), BlockPos::new(-4353, -17, -4406));
1451        assert_eq!(ChunkPos::from_entity_pos(pos), ChunkPos::new(-273, -276));
1452        assert_eq!(
1453            SectionPos::from_entity_pos(pos),
1454            SectionPos::new(-273, -2, -276)
1455        );
1456    }
1457
1458    #[test]
1459    fn packed_section_block_pos_masks_absolute_coordinates() {
1460        let packed = PackedSectionBlockPos::from_block_pos(BlockPos::new(17, -1, 18));
1461
1462        assert_eq!(packed.as_u16(), 0x12f);
1463        assert_eq!(packed.x(), 1);
1464        assert_eq!(packed.y(), 15);
1465        assert_eq!(packed.z(), 2);
1466    }
1467
1468    #[test]
1469    fn packed_section_block_pos_rejects_invalid_raw_bits() {
1470        assert!(PackedSectionBlockPos::from_raw(0x0fff).is_some());
1471        assert!(PackedSectionBlockPos::from_raw(0x1000).is_none());
1472    }
1473
1474    #[test]
1475    fn packed_section_block_pos_rejects_invalid_local_coordinates() {
1476        assert!(PackedSectionBlockPos::from_local_xyz(15, 15, 15).is_some());
1477        assert!(PackedSectionBlockPos::from_local_xyz(16, 0, 0).is_none());
1478        assert!(PackedSectionBlockPos::from_local_xyz(0, 16, 0).is_none());
1479        assert!(PackedSectionBlockPos::from_local_xyz(0, 0, 16).is_none());
1480    }
1481
1482    #[test]
1483    fn packed_section_block_pos_converts_to_absolute_block_pos() {
1484        let section = SectionPos::new(2, -4, -3);
1485        let Some(packed) = PackedSectionBlockPos::from_local_xyz(1, 15, 2) else {
1486            panic!("valid local packed section block position was rejected");
1487        };
1488
1489        assert_eq!(packed.to_block_pos(section), BlockPos::new(33, -49, -46));
1490        assert_eq!(
1491            section.relative_to_block_pos(packed),
1492            BlockPos::new(33, -49, -46)
1493        );
1494    }
1495
1496    #[test]
1497    fn packed_position_newtypes_roundtrip() {
1498        let chunk = ChunkPos::new(-12, 34);
1499        assert_eq!(PackedChunkPos::from(chunk).to_chunk_pos(), chunk);
1500
1501        let block = BlockPos::new(-1024, 64, 2048);
1502        assert_eq!(PackedBlockPos::from(block).to_block_pos(), block);
1503
1504        let section = SectionPos::new(-8, -4, 12);
1505        assert_eq!(PackedSectionPos::from(section).to_section_pos(), section);
1506    }
1507}
1508
1509/// Flags that control how a block update is processed.
1510#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1511pub struct UpdateFlags(u16);
1512
1513bitflags! {
1514    impl UpdateFlags: u16 {
1515        const UPDATE_NEIGHBORS = 1;
1516        const UPDATE_CLIENTS = 1 << 1;
1517        const UPDATE_INVISIBLE = 1 << 2;
1518        const UPDATE_IMMEDIATE = 1 << 3;
1519        const UPDATE_KNOWN_SHAPE = 1 << 4;
1520        const UPDATE_SUPPRESS_DROPS = 1 << 5;
1521        const UPDATE_MOVE_BY_PISTON = 1 << 6;
1522        const UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE = 1 << 7;
1523        const UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS = 1 << 8;
1524        const UPDATE_SKIP_ON_PLACE = 1 << 9;
1525
1526        const UPDATE_NONE = Self::UPDATE_INVISIBLE.bits() | Self::UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS.bits();
1527        const UPDATE_ALL = Self::UPDATE_NEIGHBORS.bits() | Self::UPDATE_CLIENTS.bits();
1528        const UPDATE_ALL_IMMEDIATE = Self::UPDATE_ALL.bits() | Self::UPDATE_IMMEDIATE.bits();
1529    }
1530}