//! Modification data model stub (D-111/D-112, #567). //! //! Entry point for the future player construction system (DLC scope). //! Tracks player-placed modifications to static and mobile chunks. //! No construction logic ships in v0.1 — this is a data model stub only. //! //! The `Modifications` component can be attached to any entity that represents //! a spatial chunk (static or mobile). Currently unused at runtime; the //! `SaveStateV1` field ensures the data model round-trips through save/load //! so future DLC can populate it without a save format migration. use bevy_ecs::prelude::*; use serde::{Deserialize, Serialize}; use crate::simulation::movement::TilePosition; /// A single player-placed modification to a chunk. /// /// Records what was placed, where, and when. The `modification_type` enum /// will be extended by the construction DLC — the stub contains only a /// `Placeholder` variant to keep the enum non-empty. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Modification { /// Tile position of this modification within the chunk. pub position: TilePosition, /// What kind of modification was placed. pub modification_type: ModificationType, /// Simulation tick when this modification was placed. pub placed_at_tick: u64, } /// Type of modification placed by the player. /// /// Stub enum — will be extended by the construction DLC with variants /// like `Wall`, `Floor`, `Furniture`, `Terminal`, etc. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ModificationType { /// Placeholder variant. Prevents the enum from being uninhabited /// and allows save format round-tripping before real variants ship. Placeholder, } /// Component: player-placed modifications on a chunk entity. /// /// Attach to any entity that represents a modifiable spatial region /// (static chunk, mobile chunk, etc.). Initially empty for all entities. #[derive(Component, Debug, Clone, Default, Serialize, Deserialize)] pub struct Modifications { pub entries: Vec, } #[cfg(test)] mod tests { use super::*; #[test] fn modification_type_placeholder_serializes() { let mod_type = ModificationType::Placeholder; let bytes = rmp_serde::to_vec_named(&mod_type).expect("serialize"); let recovered: ModificationType = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(mod_type, recovered); } #[test] fn modification_roundtrips_via_messagepack() { let modification = Modification { position: TilePosition::new(5, 10, 0), modification_type: ModificationType::Placeholder, placed_at_tick: 42, }; let bytes = rmp_serde::to_vec_named(&modification).expect("serialize"); let recovered: Modification = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(modification, recovered); } #[test] fn modifications_component_defaults_to_empty() { let mods = Modifications::default(); assert!(mods.entries.is_empty()); } #[test] fn modifications_with_entries_roundtrips() { let mods = Modifications { entries: vec![ Modification { position: TilePosition::new(1, 2, 0), modification_type: ModificationType::Placeholder, placed_at_tick: 100, }, Modification { position: TilePosition::new(3, 4, 1), modification_type: ModificationType::Placeholder, placed_at_tick: 200, }, ], }; let bytes = rmp_serde::to_vec_named(&mods).expect("serialize"); let recovered: Modifications = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(mods.entries.len(), recovered.entries.len()); assert_eq!(mods.entries[0], recovered.entries[0]); assert_eq!(mods.entries[1], recovered.entries[1]); } #[test] fn empty_modifications_roundtrips() { let mods = Modifications::default(); let bytes = rmp_serde::to_vec_named(&mods).expect("serialize"); let recovered: Modifications = rmp_serde::from_slice(&bytes).expect("deserialize"); assert!(recovered.entries.is_empty()); } }