feat(simulation): name dedup, behavior dedup, relationship pipeline, Want/State layer (#628 #629 #631 #632)
Four generator spike improvements in one pass: - #628: Fix name pool first-pick bias. build_name_pool now derives a zone+culture-specific ChaCha20 RNG via FNV-1a mixing of (seed, zone_type, culture_id), isolating name ordering from main RNG consumption. Different zone types with the same seed now produce different first names. - #629: Behavior dedup within a zone run. build_behavior_pools pre-shuffles each role's behavior list; gen_behaviors draws without replacement. Falls back to random repeat with warning when pool exhausts. - #631: Relationship-to-behavior pipeline. Third generation pass (~50% chance) replaces primary behavior with relationship-revealing action — rivals talk past each other, friends drift together, subordinates defer. - #632: Want/State layer. NpcWant enum (Neutral/Bored/Alert/Suspicious/ AvoidingSomeone/LookingForInfo) biased by traits and role. Fourth generation pass produces observable tells that leak internal state through behavior. AvoidingSomeone resolves against negative-valence relationships for named targets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -150,6 +150,29 @@ pub struct CulturalValues {
|
||||
// NPC blueprint (output — generator produces these)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Internal motive/state driving an NPC's micro-behavior (#632).
|
||||
///
|
||||
/// The player never sees this directly — it leaks through the observable
|
||||
/// behavior as a tell. The gap between what an NPC is doing and WHY is
|
||||
/// the asymmetric information mechanic (D-010 principle 2).
|
||||
///
|
||||
/// `Neutral` means no notable motive — the NPC is simply doing their job.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum NpcWant {
|
||||
/// Nothing stands out; routine work mode.
|
||||
Neutral,
|
||||
/// Disengaged from current task; attention elsewhere.
|
||||
Bored,
|
||||
/// Heightened attention to the environment; scanning, tracking.
|
||||
Alert,
|
||||
/// Something is wrong or off; watching without being obvious about it.
|
||||
Suspicious,
|
||||
/// Steering clear of a specific person in the zone.
|
||||
AvoidingSomeone,
|
||||
/// Actively trying to pick up information by proximity or conversation.
|
||||
LookingForInfo,
|
||||
}
|
||||
|
||||
/// Generator output for a single NPC.
|
||||
///
|
||||
/// Maps to the 10-axis model (D-024) but as a serializable data record,
|
||||
@@ -163,7 +186,12 @@ pub struct NpcBlueprint {
|
||||
pub role: String,
|
||||
/// Personality traits (2-3, no contradictory pairs).
|
||||
pub traits: Vec<PersonalityTrait>,
|
||||
/// Internal motive that biases observable behavior (#632).
|
||||
/// The player never sees this label — they infer it from the behavior.
|
||||
pub want: NpcWant,
|
||||
/// Observable behaviors the player can witness.
|
||||
/// Index 0 is the primary role/relationship-driven action.
|
||||
/// Index 1 (if present) is the Want tell — the leak of the internal state.
|
||||
pub observable_behaviors: Vec<String>,
|
||||
/// Cultural markers derived from the culture profile.
|
||||
pub cultural_markers: CulturalMarkers,
|
||||
@@ -301,6 +329,7 @@ mod tests {
|
||||
name: "Kael".into(),
|
||||
role: "dock_worker".into(),
|
||||
traits: vec![PersonalityTrait::Bold, PersonalityTrait::Honest],
|
||||
want: NpcWant::Alert,
|
||||
observable_behaviors: vec!["works efficiently".into()],
|
||||
cultural_markers: CulturalMarkers {
|
||||
speech_register: "direct".into(),
|
||||
|
||||
Reference in New Issue
Block a user