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
+1 -52
View File
@@ -78,7 +78,6 @@ pub fn compute_observer_snapshot(
&mut NearbyInteractionBuffer,
&mut MonologueBuffer,
Option<&Stance>,
Option<&CharacterArchetype>,
Option<&mut SprintAnomalyQueue>,
Option<&CognitiveDelay>,
Option<&mut DialogueResponseBuffer>,
@@ -114,7 +113,6 @@ pub fn compute_observer_snapshot(
mut interaction_buffer,
mut monologue_buffer,
stance_opt,
archetype_opt,
mut anomaly_queue_opt,
cognitive_delay_opt,
mut dialogue_response_opt,
@@ -135,8 +133,6 @@ pub fn compute_observer_snapshot(
.map(|f| f.0)
.unwrap_or(FacingDirection::default());
let archetype = archetype_opt.copied().unwrap_or_default();
// Collect player inventory (D-065 info boundary: only own items)
let player_inventory = registry
.to_stable(observer_entity)
@@ -197,7 +193,7 @@ pub fn compute_observer_snapshot(
// Take interactions and apply Phase 2 verb filter (D-057, #422)
let mut nearby_interactions = interaction_buffer.take();
apply_phase2_verb_filter(&mut nearby_interactions, observer_kg, archetype);
apply_phase2_verb_filter(&mut nearby_interactions, observer_kg);
tracing::trace!(
"compute_observer_snapshot: tick={}, visible={}, remembered={}, tiles={}",
@@ -453,7 +449,6 @@ pub fn compute_observer_snapshot(
};
buffer.snapshot = Some(ObserverSnapshot {
version: crate::bridge::types::PROTOCOL_VERSION,
tick: time.tick,
game_time,
player_facing: facing,
@@ -654,7 +649,6 @@ fn collect_remembered_entities(
/// 1. POI priority flips (D-060) — ExamineNpc above Talk for POI entities
/// 2. Confront injection — adds Confront verb for NPCs when KnowsDetails+
/// 3. Contradiction marking — sets contradicted flag when entity knowledge is Contradicted
/// 4. Archetype label relabeling — smuggler/detective see different labels for same verb
///
/// Phase boundary: Phase 1 (interaction.rs) determines verb availability from
/// ObjectType + proximity. Phase 2 (here) reads the observer's KnowledgeGraph
@@ -663,7 +657,6 @@ fn collect_remembered_entities(
fn apply_phase2_verb_filter(
interactions: &mut [NearbyInteraction],
observer_kg: &KnowledgeGraph,
archetype: CharacterArchetype,
) {
for interaction in interactions.iter_mut() {
let stable_id = StableId(interaction.entity_id);
@@ -713,18 +706,6 @@ fn apply_phase2_verb_filter(
}
}
// --- Archetype label relabeling ---
// Phase 2 swaps verb labels based on character archetype.
// The VerbKind stays the same (same handler), only the display label changes.
// This implements D-057: "Character differentiation via Phase 2 observer
// filter, not separate verb systems."
for verb in &mut interaction.verbs {
if let Some(label) = archetype_verb_label(archetype, interaction.object_type, verb.kind)
{
verb.label = label.into();
}
}
// Re-sort after priority changes and verb additions
interaction
.verbs
@@ -732,37 +713,5 @@ fn apply_phase2_verb_filter(
}
}
/// Archetype-specific verb label overrides (#422, D-057).
///
/// Returns a replacement label for the given (archetype, object_type, verb_kind)
/// combination, or None to keep the Phase 1 default label.
///
/// v0.1: Container verbs differ by archetype. Other object types keep defaults.
/// Add match arms here for future archetype-specific labels.
fn archetype_verb_label(
archetype: CharacterArchetype,
object_type: Option<ObjectType>,
kind: VerbKind,
) -> Option<&'static str> {
match (archetype, object_type, kind) {
// Smuggler: Container verbs — physical manipulation vocabulary
(CharacterArchetype::Smuggler, Some(ObjectType::Container), VerbKind::Open) => Some("Move"),
(CharacterArchetype::Smuggler, Some(ObjectType::Container), VerbKind::Search) => {
Some("Stash")
}
// Detective: Container verbs — investigation vocabulary
(CharacterArchetype::Detective, Some(ObjectType::Container), VerbKind::Open) => {
Some("Scan")
}
(CharacterArchetype::Detective, Some(ObjectType::Container), VerbKind::Search) => {
Some("Flag")
}
// All other combinations: keep Phase 1 default label
_ => None,
}
}
#[cfg(test)]
mod tests;
+2 -170
View File
@@ -59,7 +59,6 @@ fn player_always_visible_in_snapshot() {
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().expect("snapshot should exist");
assert_eq!(snapshot.version, PROTOCOL_VERSION);
assert_eq!(snapshot.entities.len(), 1);
assert!(matches!(snapshot.entities[0].kind, EntityKind::Player));
assert_eq!(snapshot.entities[0].observation, EntityVisibility::Visible);
@@ -679,10 +678,6 @@ fn snapshot_v6_fields_default_through_pipeline() {
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().expect("snapshot should exist");
assert_eq!(
snapshot.version, PROTOCOL_VERSION,
"should be current protocol version"
);
assert_eq!(
snapshot.player_stance,
MovementStance::Walk,
@@ -694,29 +689,6 @@ fn snapshot_v6_fields_default_through_pipeline() {
);
}
#[test]
fn snapshot_v6_version_is_protocol_version() {
let mut world = setup_world(32, 32);
world.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing::default(),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
));
run_observer_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
assert_eq!(
snapshot.version,
crate::bridge::types::PROTOCOL_VERSION,
"snapshot version must match PROTOCOL_VERSION constant"
);
}
// -----------------------------------------------------------------------
// Phase 2 verb filter tests (#422, D-057)
// -----------------------------------------------------------------------
@@ -967,148 +939,9 @@ fn phase2_no_contradiction_for_active_knowledge() {
);
}
#[test]
fn phase2_smuggler_relabels_container_verbs() {
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
// Container at close range, north of player
let container = world
.spawn((
TilePosition::new(16, 15, 0),
crate::simulation::interaction::Interactable,
ObjectType::Container,
))
.id();
registry.register(container);
// Smuggler player
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing(FacingDirection::North),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
CharacterArchetype::Smuggler,
))
.id();
registry.register(player);
world.insert_resource(registry);
run_full_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
assert_eq!(snapshot.nearby_interactions.len(), 1);
let interaction = &snapshot.nearby_interactions[0];
// Container at close range: Open→"Move", Search→"Stash", Observe stays "Observe"
let open_verb = interaction.verbs.iter().find(|v| v.kind == VerbKind::Open);
let search_verb = interaction
.verbs
.iter()
.find(|v| v.kind == VerbKind::Search);
let observe_verb = interaction
.verbs
.iter()
.find(|v| v.kind == VerbKind::Observe);
assert_eq!(open_verb.unwrap().label, "Move", "smuggler Open→Move");
assert_eq!(search_verb.unwrap().label, "Stash", "smuggler Search→Stash");
assert_eq!(observe_verb.unwrap().label, "Observe", "Observe unchanged");
}
#[test]
fn phase2_detective_relabels_container_verbs() {
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
let container = world
.spawn((
TilePosition::new(16, 15, 0),
crate::simulation::interaction::Interactable,
ObjectType::Container,
))
.id();
registry.register(container);
// Detective player (explicit)
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing(FacingDirection::North),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
CharacterArchetype::Detective,
))
.id();
registry.register(player);
world.insert_resource(registry);
run_full_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
assert_eq!(snapshot.nearby_interactions.len(), 1);
let interaction = &snapshot.nearby_interactions[0];
let open_verb = interaction.verbs.iter().find(|v| v.kind == VerbKind::Open);
let search_verb = interaction
.verbs
.iter()
.find(|v| v.kind == VerbKind::Search);
assert_eq!(open_verb.unwrap().label, "Scan", "detective Open→Scan");
assert_eq!(search_verb.unwrap().label, "Flag", "detective Search→Flag");
}
#[test]
fn phase2_default_archetype_is_detective() {
// When no CharacterArchetype component attached, defaults to Detective
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
let container = world
.spawn((
TilePosition::new(16, 15, 0),
crate::simulation::interaction::Interactable,
ObjectType::Container,
))
.id();
registry.register(container);
// Player WITHOUT CharacterArchetype component
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing(FacingDirection::North),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
))
.id();
registry.register(player);
world.insert_resource(registry);
run_full_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
assert_eq!(snapshot.nearby_interactions.len(), 1);
let interaction = &snapshot.nearby_interactions[0];
// Default = Detective labels
let open_verb = interaction.verbs.iter().find(|v| v.kind == VerbKind::Open);
assert_eq!(
open_verb.unwrap().label,
"Scan",
"default archetype should use Detective labels"
);
}
#[test]
fn phase2_non_container_keeps_default_labels() {
// Readable objects should keep their default labels regardless of archetype
// Readable objects keep their default labels (post-archetype cleanup, D-032 SUPERSEDED).
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
@@ -1129,7 +962,6 @@ fn phase2_non_container_keeps_default_labels() {
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
CharacterArchetype::Smuggler,
))
.id();
registry.register(player);
@@ -1145,7 +977,7 @@ fn phase2_non_container_keeps_default_labels() {
assert_eq!(
read_verb.unwrap().label,
"Read",
"Readable labels unchanged for smuggler"
"Readable labels unchanged"
);
}