Skip to main content

nms_core/
system.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::address::GalacticAddress;
5use crate::biome::{Biome, BiomeSubType};
6
7/// Unique identifier for a star system.
8///
9/// The value is the packed 48-bit galactic address with planet index zeroed out
10/// (i.e., bits 47-44 cleared). Two systems at the same voxel coordinates but
11/// different SSI values get different IDs.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct SystemId(pub u64);
14
15impl SystemId {
16    /// Create from a `GalacticAddress` by zeroing the planet index bits.
17    pub fn from_address(addr: &GalacticAddress) -> Self {
18        // Clear the top 4 bits (planet index) of the 48-bit packed value
19        let packed = addr.packed() & 0x0FFF_FFFF_FFFF;
20        SystemId(packed)
21    }
22}
23
24/// A star system containing one or more planets.
25///
26/// The galaxy (reality index) is encoded in the `address` field.
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[non_exhaustive]
29pub struct System {
30    pub address: GalacticAddress,
31    pub name: Option<String>,
32    pub discoverer: Option<String>,
33    pub timestamp: Option<DateTime<Utc>>,
34    pub planets: Vec<Planet>,
35}
36
37impl System {
38    pub fn new(
39        address: GalacticAddress,
40        name: Option<String>,
41        discoverer: Option<String>,
42        timestamp: Option<DateTime<Utc>>,
43        planets: Vec<Planet>,
44    ) -> Self {
45        Self {
46            address,
47            name,
48            discoverer,
49            timestamp,
50            planets,
51        }
52    }
53
54    /// Galaxy index (convenience accessor for `address.reality_index`).
55    pub fn reality_index(&self) -> u8 {
56        self.address.reality_index
57    }
58}
59
60/// A planet within a star system.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62#[cfg_attr(
63    feature = "archive",
64    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
65)]
66#[non_exhaustive]
67pub struct Planet {
68    /// Planet index within the system (0-15).
69    pub index: u8,
70    pub biome: Option<Biome>,
71    pub biome_subtype: Option<BiomeSubType>,
72    /// Whether the planet has infested (biological horror) variant.
73    pub infested: bool,
74    pub name: Option<String>,
75    /// Procedural generation seed.
76    pub seed_hash: Option<u64>,
77}
78
79impl Planet {
80    pub fn new(
81        index: u8,
82        biome: Option<Biome>,
83        biome_subtype: Option<BiomeSubType>,
84        infested: bool,
85        name: Option<String>,
86        seed_hash: Option<u64>,
87    ) -> Self {
88        Self {
89            index,
90            biome,
91            biome_subtype,
92            infested,
93            name,
94            seed_hash,
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn system_reality_index_from_address() {
105        let addr = GalacticAddress::new(0, 0, 0, 0x123, 0, 42);
106        let sys = System::new(addr, None, None, None, vec![]);
107        assert_eq!(sys.reality_index(), 42);
108    }
109
110    #[test]
111    fn planet_constructor() {
112        let p = Planet::new(3, Some(Biome::Lush), None, false, Some("Eden".into()), None);
113        assert_eq!(p.index, 3);
114        assert_eq!(p.biome, Some(Biome::Lush));
115        assert!(!p.infested);
116    }
117}