Sprint 37 dead-code sweep closing out two stale supersession chains: #877 (D-167, 2026-03-24): Removes HeritageRoot type alias and ZonePaletteModifier::Heritage variant from server/src/simulation/ generator.rs. The 7 abstract heritage roots were retired in favour of the corridor cultural system; these two stubs were the only remaining references. #878 (D-032 + cascade rule): Strips the entire CharacterArchetype (Smuggler/Detective) trace from the server. Per lead direction 2026-04-21 and the development cascade (CLAUDE.md), character/NPC/ verb-differentiation/monologue code is Phase 6 detail that should not exist in code yet. The running archetype trace was pre-cascade filler, not production — production is only the client's character- creation UI and insert screens (client follow-up in #882). Deleted: - CharacterArchetype enum + StartupMessage.character_archetype field - archetype_verb_label() + archetype branch of apply_phase2_verb_filter (D-057 character-verb differentiation — marked superseded) - MonologueState.character partitioning - Gauntlet archetype plumbing (setup_gauntlet no longer takes an archetype) - server/content/schemas/drama_module.schema.yaml (zero Rust consumers) - server/content/modules/tier1/smuggling_ring_v0_1.yaml - server/tests/archetype_monologue.rs (regression guard for the removed system) - server/tests/v01_integration_playthrough.rs (archetype-dependent) Decision updates: - decisions/content.md D-032 supersession rewritten to cite the cascade (v0.2 drop invalidated the prior D-117 framing). - decisions/content.md D-035 tag taxonomy: `character` enum footnote updated; field noted as unused, do not reintroduce without a confirmed Phase 6 design. - decisions/perception.md D-057: archetype-verb differentiation marked superseded. Also bundles the types.rs version-field removal from #874 since the file was already touched here. Full trace audit in docs/architecture/sprint-37-878-audit.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-100
@@ -10,24 +10,15 @@ pub use crate::knowledge::types::{
|
||||
};
|
||||
pub use crate::simulation::time::{DayPhase, TickRate};
|
||||
|
||||
/// Wire protocol version for ObserverSnapshot.
|
||||
///
|
||||
/// Versioning strategy: flat struct + serde defaults for field evolution.
|
||||
/// Client and server are co-versioned (subprocess IPC per D-020), so protocol
|
||||
/// 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 = 23;
|
||||
|
||||
/// Handshake message sent as the very first framed message after connection (#555).
|
||||
/// Client reads this before entering the normal tick loop and validates
|
||||
/// `protocol_version` against its own `PROTOCOL_VERSION` constant.
|
||||
/// Client reads this before entering the normal tick loop, then sends StartupMessage.
|
||||
/// Wire format: MessagePack, same 4-byte length-prefixed framing as ObserverSnapshot.
|
||||
///
|
||||
/// No version field — D-192 dropped the lockstep version check. Client and server
|
||||
/// are always co-shipped (D-005); genuine schema drift surfaces as a downstream
|
||||
/// MessagePack missing-field error rather than an eager handshake rejection.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct HandshakeMessage {
|
||||
/// Must match client's PROTOCOL_VERSION or the client should disconnect.
|
||||
pub protocol_version: u8,
|
||||
}
|
||||
pub struct HandshakeMessage {}
|
||||
|
||||
/// Startup message sent by the client after receiving HandshakeMessage (#175).
|
||||
/// Contains the world seed for deterministic simulation (D-010, D-029).
|
||||
@@ -46,12 +37,6 @@ pub struct StartupMessage {
|
||||
/// Generated by SessionManager.new_game() on the client.
|
||||
/// Same seed → same EntanglementConfig → same NPC population (D-029).
|
||||
pub world_seed: u64,
|
||||
/// Character archetype selected by the player (#587).
|
||||
/// Gates monologue pool selection, verb labels, and examine text.
|
||||
/// Defaults to Detective for backward compatibility (old clients
|
||||
/// that omit this field).
|
||||
#[serde(default)]
|
||||
pub character_archetype: CharacterArchetype,
|
||||
}
|
||||
|
||||
/// The ONLY data structure crossing the client-server boundary (D-020)
|
||||
@@ -79,7 +64,8 @@ pub struct StartupMessage {
|
||||
/// v17 adds: state_hash (#85, desync detection — fast hash of player pos + NPC count + tick),
|
||||
/// sim_errors (#85, structured error reporting to client).
|
||||
/// v18 adds: debug_response (#580, debug console server — command/response wire).
|
||||
/// v19 adds: character_archetype on StartupMessage (#587), current_ticker (#591).
|
||||
/// v19 adds: current_ticker (#591). (character_archetype on StartupMessage was
|
||||
/// added in #587 and removed in Sprint 37 per D-032 purge / cascade cleanup.)
|
||||
/// 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).
|
||||
@@ -89,8 +75,6 @@ pub struct StartupMessage {
|
||||
/// Future fields: ambient sound events, HUD state (D-020 expansion).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ObserverSnapshot {
|
||||
/// Protocol version for forward compatibility. See [`PROTOCOL_VERSION`].
|
||||
pub version: u8,
|
||||
/// Simulation tick when this snapshot was produced
|
||||
pub tick: u64,
|
||||
/// Game time data for client HUD display (D-031)
|
||||
@@ -495,28 +479,6 @@ pub enum ObjectType {
|
||||
Furniture,
|
||||
}
|
||||
|
||||
/// Character archetype for Phase 2 verb filtering (#422) and monologue pool
|
||||
/// selection. Determines how the character perceives and labels interactions.
|
||||
/// v0.1: Smuggler and Detective (the two playable characters).
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
pub enum CharacterArchetype {
|
||||
/// Smuggler character — sees Move/Stash on containers, physical manipulation verbs
|
||||
Smuggler,
|
||||
/// Detective character — sees Scan/Flag on containers, investigation verbs
|
||||
#[default]
|
||||
Detective,
|
||||
}
|
||||
|
||||
impl CharacterArchetype {
|
||||
/// String key for monologue pool filtering (#587).
|
||||
pub fn as_monologue_key(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Smuggler => "smuggler",
|
||||
Self::Detective => "detective",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Semantic player actions, not raw key events (D-020)
|
||||
/// Timestamped for deterministic processing
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -1105,73 +1067,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn handshake_message_roundtrip() {
|
||||
let msg = HandshakeMessage {
|
||||
protocol_version: PROTOCOL_VERSION,
|
||||
};
|
||||
// D-192: HandshakeMessage carries no version field; roundtrip verifies
|
||||
// the empty struct serialises and deserialises cleanly.
|
||||
let msg = HandshakeMessage {};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let decoded: HandshakeMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
assert_eq!(decoded, msg);
|
||||
assert_eq!(decoded.protocol_version, PROTOCOL_VERSION);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handshake_message_rejects_wrong_version() {
|
||||
let msg = HandshakeMessage {
|
||||
protocol_version: PROTOCOL_VERSION,
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let decoded: HandshakeMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
// Simulate client-side validation: version mismatch should be detectable
|
||||
let wrong_version = PROTOCOL_VERSION.wrapping_add(1);
|
||||
assert_ne!(decoded.protocol_version, wrong_version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_message_roundtrip() {
|
||||
let msg = StartupMessage {
|
||||
world_seed: 0xDEADBEEF,
|
||||
character_archetype: CharacterArchetype::Detective,
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let decoded: StartupMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
assert_eq!(decoded, msg);
|
||||
assert_eq!(decoded.world_seed, 0xDEADBEEF);
|
||||
assert_eq!(decoded.character_archetype, CharacterArchetype::Detective);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_message_smuggler_roundtrip() {
|
||||
let msg = StartupMessage {
|
||||
world_seed: 42,
|
||||
character_archetype: CharacterArchetype::Smuggler,
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let decoded: StartupMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
assert_eq!(decoded, msg);
|
||||
assert_eq!(decoded.character_archetype, CharacterArchetype::Smuggler);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_message_missing_archetype_defaults_to_detective() {
|
||||
// Simulate an old client that sends only world_seed (no character_archetype).
|
||||
// serde(default) on StartupMessage.character_archetype should default to Detective.
|
||||
#[derive(Serialize)]
|
||||
struct OldStartupMessage {
|
||||
world_seed: u64,
|
||||
}
|
||||
let old = OldStartupMessage { world_seed: 99 };
|
||||
let bytes = rmp_serde::to_vec_named(&old).expect("serialize");
|
||||
let decoded: StartupMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
assert_eq!(decoded.world_seed, 99);
|
||||
assert_eq!(decoded.character_archetype, CharacterArchetype::Detective);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_message_zero_seed() {
|
||||
let msg = StartupMessage {
|
||||
world_seed: 0,
|
||||
character_archetype: CharacterArchetype::default(),
|
||||
};
|
||||
let msg = StartupMessage { world_seed: 0 };
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let decoded: StartupMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
assert_eq!(decoded.world_seed, 0);
|
||||
@@ -1181,7 +1098,6 @@ mod tests {
|
||||
fn startup_message_max_seed() {
|
||||
let msg = StartupMessage {
|
||||
world_seed: u64::MAX,
|
||||
character_archetype: CharacterArchetype::default(),
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let decoded: StartupMessage = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
@@ -1191,10 +1107,9 @@ mod tests {
|
||||
#[test]
|
||||
fn handshake_is_distinct_from_snapshot() {
|
||||
// HandshakeMessage and ObserverSnapshot are different types on the wire.
|
||||
// A HandshakeMessage should NOT deserialize as an ObserverSnapshot.
|
||||
let msg = HandshakeMessage {
|
||||
protocol_version: PROTOCOL_VERSION,
|
||||
};
|
||||
// A HandshakeMessage (empty map) must NOT deserialize as an ObserverSnapshot
|
||||
// because ObserverSnapshot has required fields (tick, game_time, etc.).
|
||||
let msg = HandshakeMessage {};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let result = rmp_serde::from_slice::<ObserverSnapshot>(&bytes);
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user