refactor(simulation): strip archetype trace + HeritageRoot per cascade (#877, #878)

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:
2026-04-22 08:55:48 +02:00
co-authored by Claude Opus 4.6
parent c640563fc0
commit cae3d3ab85
22 changed files with 93 additions and 2444 deletions
+3 -84
View File
@@ -4,10 +4,9 @@ use settled_reach_server::bridge::types::*;
use settled_reach_server::simulation::time::{DayPhase, TickRate};
use std::fs;
/// Helper to create a minimal v2 snapshot for tests
/// Helper to create a minimal snapshot for tests (D-192: no version field)
fn test_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
ObserverSnapshot {
version: PROTOCOL_VERSION,
tick,
game_time: GameTime {
day: 0,
@@ -65,7 +64,6 @@ fn observer_snapshot_roundtrip() {
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
assert_eq!(decoded.version, PROTOCOL_VERSION);
assert_eq!(decoded.tick, 42);
assert_eq!(decoded.entities.len(), 1);
assert_eq!(decoded.entities[0].entity_id, 1);
@@ -171,18 +169,13 @@ fn all_fixtures_deserialize() {
let bytes = fs::read(&path).unwrap_or_else(|_| panic!("read fixture {}", name));
if name.starts_with("snapshot_boundary") {
// Boundary snapshot fixtures (#472): tick may exceed PROTOCOL_VERSION check
// Boundary snapshot fixtures (#472)
rmp_serde::from_slice::<ObserverSnapshot>(&bytes).unwrap_or_else(|e| {
panic!("deserialize boundary snapshot fixture {}: {}", name, e)
});
} else if name.starts_with("snapshot") {
let snap = rmp_serde::from_slice::<ObserverSnapshot>(&bytes)
rmp_serde::from_slice::<ObserverSnapshot>(&bytes)
.unwrap_or_else(|e| panic!("deserialize snapshot fixture {}: {}", name, e));
assert_eq!(
snap.version, PROTOCOL_VERSION,
"fixture {} has wrong version",
name
);
} else if name.starts_with("input_batch") {
rmp_serde::from_slice::<Vec<PlayerInput>>(&bytes)
.unwrap_or_else(|e| panic!("deserialize batch input fixture {}: {}", name, e));
@@ -244,7 +237,6 @@ fn all_entity_kind_variants_roundtrip() {
#[test]
fn snapshot_v2_fields_roundtrip() {
let snapshot = ObserverSnapshot {
version: PROTOCOL_VERSION,
tick: 100,
game_time: GameTime {
day: 3,
@@ -311,7 +303,6 @@ fn snapshot_v2_fields_roundtrip() {
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
assert_eq!(decoded.version, PROTOCOL_VERSION);
assert_eq!(decoded.game_time.day, 3);
assert_eq!(decoded.game_time.time_of_day, 720);
assert_eq!(decoded.game_time.day_phase, DayPhase::Evening);
@@ -354,17 +345,6 @@ fn entity_to_bits_roundtrip() {
}
}
/// PROTOCOL_VERSION constant matches snapshot version field
#[test]
fn protocol_version_constant_matches_snapshot() {
let snapshot = test_snapshot(0, vec![]);
assert_eq!(snapshot.version, PROTOCOL_VERSION);
assert_eq!(
PROTOCOL_VERSION, 21,
"bump this assertion when protocol version changes"
);
}
/// All FacingDirection variants round-trip
#[test]
fn all_facing_direction_variants_roundtrip() {
@@ -381,7 +361,6 @@ fn all_facing_direction_variants_roundtrip() {
for dir in directions {
let snapshot = ObserverSnapshot {
version: PROTOCOL_VERSION,
tick: 0,
game_time: GameTime {
day: 0,
@@ -525,7 +504,6 @@ fn v5_payload_deserializes_into_v6_struct() {
.expect("v5 payload should deserialize into v6 struct via serde(default)");
// New fields should get their defaults
assert_eq!(decoded.version, 5, "version field preserved from v5");
assert_eq!(decoded.tick, 42);
assert_eq!(
decoded.player_stance,
@@ -717,23 +695,6 @@ fn verb_kind_confront_roundtrip() {
assert_eq!(decoded.nearby_interactions[0].verbs[0].label, "Confront");
}
/// CharacterArchetype enum round-trips through MessagePack (#422).
/// Used in Phase 2 label relabeling — must survive the wire.
#[test]
fn all_character_archetype_variants_roundtrip() {
let archetypes = [CharacterArchetype::Smuggler, CharacterArchetype::Detective];
for archetype in archetypes {
let bytes = rmp_serde::to_vec_named(&archetype).expect("serialize");
let decoded: CharacterArchetype = rmp_serde::from_slice(&bytes).expect("deserialize");
assert_eq!(
decoded, archetype,
"CharacterArchetype::{:?} roundtrip failed",
archetype
);
}
}
/// NearbyInteraction.contradicted=true round-trips through MessagePack (#422).
/// Guards the contradiction flag survives serialization.
#[test]
@@ -1247,7 +1208,6 @@ fn v8_payload_deserializes_into_v9_struct() {
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes)
.expect("v8 payload should deserialize into v9 struct via serde(default)");
assert_eq!(decoded.version, 8, "version field preserved from v8");
assert_eq!(decoded.tick, 100);
assert!(
decoded.blocked_entities.is_empty(),
@@ -1321,7 +1281,6 @@ fn v9_payload_deserializes_into_v10_struct() {
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes)
.expect("v9 payload should deserialize into v10 struct via serde(default)");
assert_eq!(decoded.version, 9, "version field preserved from v9");
assert_eq!(decoded.tick, 200);
assert_eq!(
decoded.rng_seed, None,
@@ -1397,7 +1356,6 @@ fn v10_payload_deserializes_into_v11_struct() {
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes)
.expect("v10 payload should deserialize into v11 struct via serde(default)");
assert_eq!(decoded.version, 10, "version field preserved from v10");
assert_eq!(decoded.tick, 300);
assert_eq!(decoded.visible_tiles.len(), 1);
assert_eq!(
@@ -1458,8 +1416,6 @@ fn serde_default_fields_fill_in_when_missing_from_wire() {
let decoded: ObserverSnapshot =
serde_json::from_value(minimal_json).expect("minimal JSON must deserialize");
// Version matches what was in the wire
assert_eq!(decoded.version, 13);
assert_eq!(decoded.tick, 42);
assert_eq!(decoded.entities.len(), 1);
@@ -1492,28 +1448,6 @@ fn serde_default_fields_fill_in_when_missing_from_wire() {
);
}
/// A snapshot with version != PROTOCOL_VERSION can be detected by checking
/// the version field after deserialization (#232 compatibility checking).
#[test]
fn snapshot_version_mismatch_is_detectable() {
let mut snapshot = test_snapshot(0, vec![]);
let future_version: u8 = PROTOCOL_VERSION + 1;
snapshot.version = future_version;
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
// The version field faithfully preserves the value — caller detects mismatch
assert_eq!(
decoded.version, future_version,
"version field must survive round-trip unchanged"
);
assert_ne!(
decoded.version, PROTOCOL_VERSION,
"client should detect this as a version mismatch"
);
}
/// tell_state=None is skipped in msgpack serialization (skip_serializing_if).
/// A snapshot with tell_state=None produces fewer bytes than one with
/// tell_state=Some(Nervous) — demonstrates the skip_serializing_if contract.
@@ -1627,19 +1561,6 @@ fn all_verb_kind_variants_roundtrip_v232() {
}
}
/// PROTOCOL_VERSION u8 type fits in one byte — wire overhead is minimal (#232).
/// This guards against accidental widening of the version type.
#[test]
fn protocol_version_fits_in_u8() {
// u8 max is 255 — enough for ~242 more protocol iterations.
// If PROTOCOL_VERSION ever reaches 200, consider migrating to u16.
assert!(
PROTOCOL_VERSION <= 200,
"PROTOCOL_VERSION={} is approaching u8 saturation; consider widening the type",
PROTOCOL_VERSION
);
}
/// NearbyInteraction.object_type round-trips through MessagePack (#422).
/// Verifies object_type=Some(Container) survives the wire.
#[test]
@@ -1687,7 +1608,6 @@ fn fixture_snapshot_minimal_fields() {
let snap: ObserverSnapshot =
rmp_serde::from_slice(&bytes).expect("deserialize snapshot_minimal");
assert_eq!(snap.version, PROTOCOL_VERSION, "protocol version mismatch");
assert_eq!(snap.tick, 0, "tick should be 0");
assert_eq!(snap.entities.len(), 1, "should have exactly 1 entity");
assert_eq!(snap.entities[0].entity_id, 1);
@@ -1707,7 +1627,6 @@ fn fixture_snapshot_full_fields() {
let bytes = read_named_fixture("snapshot_full");
let snap: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize snapshot_full");
assert_eq!(snap.version, PROTOCOL_VERSION, "protocol version mismatch");
assert_eq!(snap.tick, 42, "tick should be 42");
// Monologue