feat(simulation): bookmark definition system with bridge protocol (#614)
BookmarkPlugin, BookmarkRegistry, SelectedBookmark resources. Tycoon bookmark defined; PROTOCOL_VERSION bumped to 22. RequestBookmarkCatalog + ConfirmBookmark actions wired into process_player_input via BookmarkInputParams SystemParam bundle (resolves Bevy's 16-system-param limit). build_catalog() accepts optional CultureResolver for D-128 location-culture mapping. Snapshot delivery at tick-0 via SnapshotBuffer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -321,6 +321,7 @@ mod tests {
|
||||
current_ticker: None,
|
||||
settings_response: None,
|
||||
economy_snapshot: None,
|
||||
bookmark_catalog: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,6 +464,7 @@ mod tests {
|
||||
current_ticker: None,
|
||||
settings_response: None,
|
||||
economy_snapshot: None,
|
||||
bookmark_catalog: None,
|
||||
};
|
||||
let text = format_snapshot_text(&snap);
|
||||
assert!(text.contains("Tick 0"));
|
||||
|
||||
@@ -17,7 +17,7 @@ pub use crate::simulation::time::{DayPhase, TickRate};
|
||||
/// negotiation is unnecessary. Client should reject snapshots with version !=
|
||||
/// PROTOCOL_VERSION. New fields use #[serde(default)] only during the migration
|
||||
/// period, then the default is removed once both sides are updated.
|
||||
pub const PROTOCOL_VERSION: u8 = 21;
|
||||
pub const PROTOCOL_VERSION: u8 = 22;
|
||||
|
||||
/// Handshake message sent as the very first framed message after connection (#555).
|
||||
/// Client reads this before entering the normal tick loop and validates
|
||||
@@ -83,6 +83,8 @@ pub struct StartupMessage {
|
||||
/// v20 adds: settings_response (#627, SQLite settings IPC).
|
||||
/// v21 adds: economy_snapshot (#822, D-181 7-signal snapshot per queried system),
|
||||
/// EconStateQuery PlayerAction variant (#822).
|
||||
/// v22 adds: bookmark_catalog (#614, D-115/D-117 CK3-style bookmark system),
|
||||
/// RequestBookmarkCatalog + ConfirmBookmark PlayerAction variants (#614).
|
||||
/// Future fields: ambient sound events, HUD state (D-020 expansion).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ObserverSnapshot {
|
||||
@@ -224,6 +226,56 @@ pub struct ObserverSnapshot {
|
||||
/// None during normal gameplay; client queries explicitly via `EconStateQuery`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub economy_snapshot: Option<EconomySnapshot>,
|
||||
/// Bookmark catalog (#614, D-115/D-117).
|
||||
/// Present for exactly one tick after handshake (tick 0) and after
|
||||
/// `PlayerAction::RequestBookmarkCatalog`. None otherwise.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub bookmark_catalog: Option<BookmarkCatalog>,
|
||||
}
|
||||
|
||||
/// Career kind wire-safe mirror for `CareerKind` (#614, D-117).
|
||||
///
|
||||
/// Kept separate from the server-internal `CareerKind` so future server-only
|
||||
/// variants (e.g. debug/test archetypes) don't leak through serde.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CareerKindWire {
|
||||
Tycoon,
|
||||
}
|
||||
|
||||
impl From<crate::bookmark::types::CareerKind> for CareerKindWire {
|
||||
fn from(k: crate::bookmark::types::CareerKind) -> Self {
|
||||
match k {
|
||||
crate::bookmark::types::CareerKind::Tycoon => CareerKindWire::Tycoon,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bookmark projection for the client (#614, D-115/D-117).
|
||||
///
|
||||
/// Sent inside `BookmarkCatalog` in `ObserverSnapshot.bookmark_catalog`.
|
||||
/// Drop server-only fields; keep the struct stable for the bridge.
|
||||
/// `allowed_locations_cultures` is pre-resolved server-side (§9 of culture spec, #679).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct BookmarkWire {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub subtitle: String,
|
||||
pub flavor: String,
|
||||
pub default_location: String,
|
||||
pub allowed_locations: Vec<String>,
|
||||
/// Parallel to `allowed_locations`: resolved culture tag per location (#679).
|
||||
/// Populated by `BookmarkRegistry::build_catalog` once CultureResolverResource
|
||||
/// is available. Empty string placeholder until #679 lands.
|
||||
pub allowed_locations_cultures: Vec<String>,
|
||||
pub career: CareerKindWire,
|
||||
pub starting_capital_tractus: i64,
|
||||
}
|
||||
|
||||
/// Catalog of all available bookmarks sent to the client (#614).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BookmarkCatalog {
|
||||
pub bookmarks: Vec<BookmarkWire>,
|
||||
}
|
||||
|
||||
/// A single news ticker headline crossing the wire boundary (#591).
|
||||
@@ -564,6 +616,19 @@ pub enum PlayerAction {
|
||||
EconStateQuery {
|
||||
system_id: String,
|
||||
},
|
||||
/// Client requests the full bookmark catalog (#614).
|
||||
/// Server responds with `ObserverSnapshot.bookmark_catalog` in the next tick.
|
||||
RequestBookmarkCatalog,
|
||||
/// Player confirms character creation with a chosen bookmark (#614, #618).
|
||||
///
|
||||
/// `bookmark_id` must match a known `BookmarkId`. `starting_location_id` must
|
||||
/// be in `BookmarkDefinition.allowed_locations` for that bookmark.
|
||||
/// On invalid inputs: server pushes a `SimError { kind: ProtocolError }` and
|
||||
/// ignores the action — client should block the confirm button and re-prompt.
|
||||
ConfirmBookmark {
|
||||
bookmark_id: String,
|
||||
starting_location_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl PlayerAction {
|
||||
@@ -1038,6 +1103,8 @@ pub struct SnapshotBuffer {
|
||||
pub pending_settings_response: Option<crate::settings::types::SettingsResponseWire>,
|
||||
/// Pending economy snapshot, consumed once by `compute_observer_snapshot` (#822).
|
||||
pub pending_economy_response: Option<EconomySnapshot>,
|
||||
/// Pending bookmark catalog, consumed once by `compute_observer_snapshot` (#614).
|
||||
pub pending_bookmark_catalog: Option<BookmarkCatalog>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user