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
+48 -76
View File
@@ -14,7 +14,6 @@
use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
use crate::bridge::types::CharacterArchetype;
use crate::knowledge::events::{KnowledgeEvent, KnowledgeEventQueue, KnowledgeEventType};
use crate::knowledge::EntityRegistry;
use crate::npc::mood::{MoodState, NpcMood};
@@ -37,11 +36,12 @@ pub struct ExamineRequest {
pub target: Entity,
}
/// Character-filtered examination result for snapshot delivery.
/// Examination result for snapshot delivery.
///
/// Content differs per CharacterArchetype:
/// Smuggler — physical threat read, cargo-handling posture, opportunity windows.
/// Detective — procedural tells, behavioral inconsistencies, stress indicators.
/// Phase 6 note: per-archetype text variants (smuggler/detective flavor) were
/// removed during the cascade cleanup. Text is a single unified "subject read"
/// until archetype differentiation is reintroduced per the culture-driven
/// voice system (D-121) in a later cascade phase.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExamineResultEvent {
/// Character-filtered observation text for client display.
@@ -106,12 +106,15 @@ fn has_trait(traits_opt: Option<&PersonalityTraits>, t: PersonalityTrait) -> boo
traits_opt.map(|p| p.traits.contains(&t)).unwrap_or(false)
}
/// Generate character-filtered examination text from NPC component state.
/// Generate examination text from NPC component state.
/// All logic is pure, deterministic, and integer-based (D-010).
///
/// Archetype-specific text variants were removed during the cascade cleanup
/// (D-032 / Sprint 37). Reintroduce per-culture voice when the cascade reaches
/// the Phase 6 character/NPC layer (see D-121).
pub fn generate_examine_text(
mood: NpcMood,
ratio: u8,
archetype: CharacterArchetype,
traits_opt: Option<&PersonalityTraits>,
) -> String {
let stress_label = match ratio {
@@ -123,53 +126,29 @@ pub fn generate_examine_text(
let mood_label = mood_word(mood);
match archetype {
CharacterArchetype::Smuggler => {
// Physical threat read + cargo opportunity window
let threat = if matches!(mood, NpcMood::Hostile | NpcMood::Suspicious) {
"Threat posture. Don't push it."
} else if has_trait(traits_opt, PersonalityTrait::Bold) {
"Confident bearing. Will push back if cornered."
} else if has_trait(traits_opt, PersonalityTrait::Cautious) {
"Nervous type. Predictable under pressure."
} else {
"No obvious threat read."
};
let tell = if has_trait(traits_opt, PersonalityTrait::Deceptive) {
"Controlled affect — practiced concealment."
} else if matches!(mood, NpcMood::Anxious | NpcMood::Frustrated) {
"Involuntary stress markers present."
} else if matches!(mood, NpcMood::Suspicious) {
"Scanning. Aware of being observed."
} else if matches!(mood, NpcMood::Hostile) {
"Threat posture. Aware of being observed."
} else {
"Baseline presentation."
};
let window = if ratio > 60 {
"Too distracted to track cargo movement."
} else if matches!(mood, NpcMood::Focused) {
"Paying close attention to this section."
} else {
"Standard patrol pattern. Window is there."
};
let read = if ratio > 60 {
"Under pressure — potential liability or asset."
} else if matches!(mood, NpcMood::Content | NpcMood::Warm) {
"Comfortable. Less guarded than usual."
} else if matches!(mood, NpcMood::Focused) {
"Paying close attention."
} else {
"Routine behavior pattern."
};
format!("Appears {mood_label}, {stress_label}. {threat} {window}")
}
CharacterArchetype::Detective => {
// Procedural tells + behavioral read
let tell = if has_trait(traits_opt, PersonalityTrait::Deceptive) {
"Controlled affect — practiced concealment."
} else if matches!(mood, NpcMood::Anxious | NpcMood::Frustrated) {
"Involuntary stress markers present."
} else if matches!(mood, NpcMood::Suspicious) {
"Scanning. Aware of being observed."
} else {
"Baseline presentation."
};
let read = if ratio > 60 {
"Under pressure — potential liability or asset."
} else if matches!(mood, NpcMood::Content | NpcMood::Warm) {
"Comfortable. Less guarded than usual."
} else {
"Routine behavior pattern."
};
format!("Subject: {mood_label}, {stress_label}. {tell} {read}")
}
}
format!("Subject: {mood_label}, {stress_label}. {tell} {read}")
}
// ---------------------------------------------------------------------------
@@ -196,7 +175,6 @@ pub fn process_examine_interaction(
Entity,
&TilePosition,
&ExamineRequest,
Option<&CharacterArchetype>,
&mut ExamineResultBuffer,
),
With<PlayerCharacter>,
@@ -212,14 +190,13 @@ pub fn process_examine_interaction(
>,
examine_text_query: Query<(&TilePosition, Option<&ExamineText>)>,
) {
let Ok((player_entity, player_pos, examine_req, archetype_opt, mut result_buffer)) =
let Ok((player_entity, player_pos, examine_req, mut result_buffer)) =
player_query.single_mut()
else {
return;
};
let target = examine_req.target;
let archetype = archetype_opt.copied().unwrap_or_default();
// Try NPC examine path first
if let Ok((target_pos, mood_opt, tolerance_opt, traits_opt)) = npc_query.get(target) {
@@ -238,7 +215,7 @@ pub fn process_examine_interaction(
let mood = mood_opt.map(|m| m.mood).unwrap_or(NpcMood::Neutral);
let ratio = tolerance_opt.map(stress_ratio).unwrap_or(0);
let text = generate_examine_text(mood, ratio, archetype, traits_opt);
let text = generate_examine_text(mood, ratio, traits_opt);
kg_events.push(KnowledgeEvent {
observer: player_entity,
@@ -323,8 +300,8 @@ mod tests {
}
#[test]
fn smuggler_hostile_npc_gives_threat_read() {
let text = generate_examine_text(NpcMood::Hostile, 20, CharacterArchetype::Smuggler, None);
fn hostile_npc_gives_threat_read() {
let text = generate_examine_text(NpcMood::Hostile, 20, None);
assert!(
text.contains("Threat posture"),
"expected threat read, got: {text}"
@@ -332,32 +309,27 @@ mod tests {
}
#[test]
fn smuggler_focused_npc_notes_attention() {
let text = generate_examine_text(NpcMood::Focused, 30, CharacterArchetype::Smuggler, None);
fn focused_npc_notes_attention() {
let text = generate_examine_text(NpcMood::Focused, 30, None);
assert!(
text.contains("close attention"),
text.contains("Paying close attention"),
"expected attention note, got: {text}"
);
}
#[test]
fn smuggler_high_stress_identifies_distraction() {
let text = generate_examine_text(NpcMood::Anxious, 80, CharacterArchetype::Smuggler, None);
fn high_stress_identifies_pressure() {
let text = generate_examine_text(NpcMood::Anxious, 80, None);
assert!(
text.contains("Too distracted"),
"expected distraction read, got: {text}"
text.contains("Under pressure"),
"expected pressure read, got: {text}"
);
}
#[test]
fn detective_deceptive_npc_notes_concealment() {
fn deceptive_npc_notes_concealment() {
let t = traits(&[PersonalityTrait::Deceptive]);
let text = generate_examine_text(
NpcMood::Neutral,
20,
CharacterArchetype::Detective,
Some(&t),
);
let text = generate_examine_text(NpcMood::Neutral, 20, Some(&t));
assert!(
text.contains("Controlled affect"),
"expected concealment note, got: {text}"
@@ -365,8 +337,8 @@ mod tests {
}
#[test]
fn detective_anxious_npc_notes_stress_markers() {
let text = generate_examine_text(NpcMood::Anxious, 50, CharacterArchetype::Detective, None);
fn anxious_npc_notes_stress_markers() {
let text = generate_examine_text(NpcMood::Anxious, 50, None);
assert!(
text.contains("stress markers"),
"expected stress markers, got: {text}"
@@ -374,8 +346,8 @@ mod tests {
}
#[test]
fn detective_content_npc_notes_low_guard() {
let text = generate_examine_text(NpcMood::Content, 10, CharacterArchetype::Detective, None);
fn content_npc_notes_low_guard() {
let text = generate_examine_text(NpcMood::Content, 10, None);
assert!(
text.contains("Less guarded"),
"expected low guard note, got: {text}"
-3
View File
@@ -72,8 +72,6 @@ pub type EconomicModifier = String;
pub type FactionModifier = String;
/// Condition modifier on a zone palette (worn, pristine, damaged). Stub.
pub type ConditionModifier = String;
/// Heritage root modifier (Settled Reach cultural grammar layer). Stub.
pub type HeritageRoot = String;
/// Season modifier (affects palette and ambient conditions). Stub.
pub type Season = String;
/// Role slot within a social site template. Stub.
@@ -366,7 +364,6 @@ pub enum PaletteModifier {
Era(Era),
FactionPresence(FactionModifier),
Condition(ConditionModifier),
Heritage(HeritageRoot),
Season(Season),
}
-4
View File
@@ -131,8 +131,6 @@ pub struct MonologueState {
pub entered: bool,
/// IDs of lines already shown (dedup within session).
pub shown_ids: BTreeSet<String>,
/// Character type for pool filtering. Set from CharacterArchetype (#587).
pub character: String,
/// Tick of the last observation event we reacted to (#119, observe_npc).
/// Observation events arrive one tick after the snapshot that caused them,
/// so we track which tick's events we've already processed.
@@ -147,8 +145,6 @@ impl Default for MonologueState {
idle_ticks: 0,
entered: false,
shown_ids: BTreeSet::new(),
// Default to detective; overridden by CharacterArchetype at spawn (#587)
character: "detective".to_string(),
last_observation_tick: 0,
}
}