🔴 Breaking Analysis: The Secret Traitor / Red Cloak Explained → Read Now

Part IV: Computational Modelling

Chapter 10: Emotion and Deception Engine

~6,000 words

Abstract

This chapter presents the computational systems for modelling emotional states, deception behaviours, and secret management in The Traitors simulation. I develop a multi-layered emotion model that distinguishes internal states from displayed behaviours, formalize the "masking strain" concept that produces detectable tells, and specify the secret management system that governs information boundaries. The resulting engine enables AI agents to lie, conceal, and leak information in ways that mirror human deception patterns.

10.1 Introduction: The Authenticity Challenge

The core challenge in simulating The Traitors is authenticity: not just generating plausible dialogue, but generating dialogue that reflects the psychological complexity that makes the format compelling to audiences worldwide:

  • Internal emotional states (often hidden)
  • Strategic calculations (role-dependent)
  • Accumulated strain from maintaining deception
  • Relationship dynamics that evolve over time

Simple chatbots fail because they lack internal state. The emotion and deception engine provides the hidden architecture that makes external behaviour meaningful. This builds on the RAG architecture described in the previous chapter, adding the emotional and deceptive layers that transform retrieval into authentic social simulation.

10.2 Emotional State Modelling

10.2.1 Core Emotion Framework

The system tracks six primary emotions relevant to gameplay:

Emotion Range Description Behavioural Effect
Fear [0, 1] Anxiety about elimination Defensive posture, reduced risk-taking
Paranoia [0, 1] Suspicion of others Increased accusations, alliance testing (see information asymmetry)
Guilt [0, 1] Remorse for actions Overexplanation, avoidance, confession pressure
Confidence [0, 1] Self-assurance Bold moves, leadership (as seen in strategic archetypes), potential overreach
Anger [0, 1] Frustration with others Confrontation, alliance fractures
Trust [0, 1]* Faith in specific others Alliance maintenance, vote coordination

*Trust is tracked per-relationship, not globally.

10.2.2 Emotion State Structure

type EmotionState struct {
    Fear       float64
    Paranoia   float64
    Guilt      float64
    Confidence float64
    Anger      float64

    // Per-target emotions
    TrustMap     map[PlayerID]float64
    SuspicionMap map[PlayerID]float64
    AngerMap     map[PlayerID]float64
}

func NewEmotionState(personality *Personality) *EmotionState {
    return &EmotionState{
        Fear:       personality.BaseFear,
        Paranoia:   personality.BaseParanoia,
        Guilt:      0.0,
        Confidence: personality.BaseConfidence,
        Anger:      0.0,
        TrustMap:   make(map[PlayerID]float64),
        SuspicionMap: make(map[PlayerID]float64),
        AngerMap:   make(map[PlayerID]float64),
    }
}
        

10.2.3 Emotion Triggers

Events modify emotional states:

Murder Reveal:

func (e *EmotionState) ProcessMurderReveal(victim PlayerID, relationship float64) {
    if relationship > 0.5 {  // Close relationship with victim
        e.Anger += 0.3 * relationship
        e.Fear += 0.2
        e.Paranoia += 0.25
    } else {
        e.Fear += 0.1
        e.Paranoia += 0.15
    }
    e.clampValues()
}
        

Accusation Received:

func (e *EmotionState) ProcessAccusation(accuser PlayerID, strength float64) {
    e.Fear += 0.2 * strength
    e.Anger += 0.15 * strength
    e.AngerMap[accuser] += 0.3 * strength
    e.TrustMap[accuser] -= 0.4 * strength
    e.clampValues()
}
        

Correct Identification (Traitor Caught):

func (e *EmotionState) ProcessTraitorCaught() {
    e.Confidence += 0.2
    e.Fear -= 0.15
    e.Paranoia -= 0.1
    e.clampValues()
}
        

10.2.4 Emotion Decay and Reinforcement

Decay Function (per phase):

func (e *EmotionState) ApplyDecay() {
    const decayRate = 0.05

    e.Fear = max(e.Fear - decayRate, e.baseFear)
    e.Anger = max(e.Anger - decayRate, 0.0)
    e.Guilt = max(e.Guilt - decayRate * 0.5, 0.0)  // Guilt decays slower

    // Trust and suspicion decay toward neutral
    for id := range e.TrustMap {
        e.TrustMap[id] *= 0.98  // Slow drift toward neutral
    }
}
        

Reinforcement: Repeated events compound effect:

func (e *EmotionState) ReinforceParanoia(intensity float64) {
    reinforcementFactor := 1.0 + (e.Paranoia * 0.5)  // Higher base = stronger effect
    e.Paranoia += intensity * reinforcementFactor
    e.clampValues()
}
        

10.3 The Deception Engine

10.3.1 Displayed vs. Internal Emotion

The key insight: what a player shows differs from what they feel.

type DeceptionState struct {
    InternalEmotion  EmotionState
    DisplayedEmotion EmotionState
    MaskingStrain    float64
    DeceptionMode    DeceptionMode
}

type DeceptionMode int
const (
    Authentic     DeceptionMode = iota  // Showing true feelings
    Suppressing                          // Hiding negative emotions
    Amplifying                           // Exaggerating emotions
    Fabricating                          // Showing opposite of truth
)
        

10.3.2 Masking Strain Accumulation

Maintaining deception creates strain that can produce tells:

type MaskingStrain struct {
    CurrentStrain   float64  // [0, 1]
    StrainHistory   []float64
    BreakingPoint   float64  // Personality-dependent threshold
    RecoveryRate    float64  // How quickly strain decreases
}

func (m *MaskingStrain) AccumulateStrain(
    internalState EmotionState,
    displayedState EmotionState,
    deceptionDifficulty float64,
) {
    // Calculate divergence between internal and displayed
    emotionGap := m.calculateGap(internalState, displayedState)

    // Strain increases with gap and difficulty
    strainIncrease := emotionGap * deceptionDifficulty * 0.1

    m.CurrentStrain = min(m.CurrentStrain + strainIncrease, 1.0)
    m.StrainHistory = append(m.StrainHistory, m.CurrentStrain)
}

func (m *MaskingStrain) calculateGap(internal, displayed EmotionState) float64 {
    // Sum of absolute differences across emotions
    gap := 0.0
    gap += abs(internal.Fear - displayed.Fear)
    gap += abs(internal.Guilt - displayed.Guilt)
    gap += abs(internal.Confidence - displayed.Confidence)
    gap += abs(internal.Anger - displayed.Anger)
    return gap / 4.0  // Normalize
}
        

10.3.3 Strain Thresholds and Tells

When strain exceeds thresholds, tells emerge:

const (
    StrainLow     = 0.3  // Minor tells possible
    StrainMedium  = 0.5  // Noticeable tells likely
    StrainHigh    = 0.7  // Significant tells certain
    StrainCritic  = 0.9  // Breakdown risk
)

func (m *MaskingStrain) GenerateTells() []Tell {
    tells := []Tell{}

    if m.CurrentStrain > StrainLow {
        tells = append(tells, m.selectTell(TellMinor))
    }
    if m.CurrentStrain > StrainMedium {
        tells = append(tells, m.selectTell(TellNoticeable))
    }
    if m.CurrentStrain > StrainHigh {
        tells = append(tells, m.selectTell(TellSignificant))
    }
    if m.CurrentStrain > StrainCritic {
        // Potential emotional breakdown
        tells = append(tells, m.selectTell(TellCritical))
    }

    return tells
}
        

10.3.4 Deception Types

The engine models different deception strategies:

Type Description Strain Level Detection Difficulty
Omission Not mentioning relevant information Low Medium
Minimization Downplaying significance Low Medium
Misdirection Redirecting attention elsewhere Medium Medium-High
Fabrication Creating false information High Variable
Denial Directly denying truth Medium-High Medium
Bluffing Acting opposite to actual position High High
type DeceptionType int
const (
    Omission DeceptionType = iota
    Minimization
    Misdirection
    Fabrication
    Denial
    Bluffing
)

func (d *DeceptionEngine) SelectDeceptionType(
    situation Situation,
    personality Personality,
    strainLevel float64,
) DeceptionType {
    // Higher strain → prefer lower-effort deceptions
    // More confident personality → willing to attempt fabrication
    // Situation determines appropriateness
}
        

10.4 Secret Management

Secret management is fundamental to the information asymmetry that defines The Traitors. The system must track what each player knows, what they are hiding, and how that hidden information creates psychological pressure.

10.4.1 Secret Types

type SecretType int
const (
    RoleSecret       SecretType = iota  // Traitor identity
    AllianceSecret                       // Hidden partnerships
    KnowledgeSecret                      // Private observations
    PlanSecret                           // Strategic intentions
)

type Secret struct {
    Type        SecretType
    Content     interface{}
    Importance  float64    // How critical to maintain
    Age         int        // Phases since creation
    ShareableWith []PlayerID  // Who can know
    LeakRisk    float64    // Current risk of accidental revelation
}
        

10.4.2 Secret Pressure

Secrets create psychological pressure:

type SecretPressure struct {
    TotalPressure  float64
    Secrets        []*Secret
    ConfessionUrge float64  // Builds over time
}

func (sp *SecretPressure) CalculatePressure() float64 {
    total := 0.0
    for _, secret := range sp.Secrets {
        secretPressure := secret.Importance * (1.0 + float64(secret.Age) * 0.05)
        total += secretPressure
    }
    return min(total, 1.0)
}

func (sp *SecretPressure) UpdateConfessionUrge(emotionState EmotionState) {
    // Guilt increases confession urge
    guildFactor := emotionState.Guilt * 0.3

    // Trust in listener increases confession urge
    // (handled in conversation context)

    sp.ConfessionUrge = min(sp.ConfessionUrge + guildFactor, 1.0)
}
        

10.4.3 Temporal Secret Evolution

Secrets change over time:

func (s *Secret) Age() {
    s.Age++

    // Old secrets become harder to maintain
    if s.Age > 5 {
        s.LeakRisk += 0.02 * float64(s.Age - 5)
    }

    // But also potentially less relevant
    if s.Type == KnowledgeSecret && s.Age > 10 {
        s.Importance *= 0.95
    }
}
        

10.5 Deception Indicators

10.5.1 Observable Indicators

When strain is high, indicators appear in dialogue:

Indicator Weight Observable Pattern
Vague Answers 0.15 Non-specific responses to specific questions
Deflection 0.20 Redirecting topic instead of answering
Overexplanation 0.18 More detail than necessary
Emotional Mismatch 0.25 Response tone doesn't match content
Inconsistency 0.30 Contradicting previous statements
Defensive Posture 0.22 Hostile response to neutral inquiry

10.5.2 Indicator Generation

type DeceptionIndicator struct {
    Type        IndicatorType
    Severity    float64
    Observable  bool  // Whether other players can detect
    Description string
}

func (d *DeceptionEngine) GenerateIndicators(
    player *PlayerState,
    responseContent string,
    situation Situation,
) []DeceptionIndicator {

    indicators := []DeceptionIndicator{}
    strain := player.MaskingStrain.CurrentStrain

    // Probability of each indicator based on strain
    if strain > 0.3 && rand.Float64() < strain {
        indicators = append(indicators, d.generateVaguenessIndicator(responseContent))
    }

    if strain > 0.4 && rand.Float64() < strain * 0.8 {
        indicators = append(indicators, d.generateDeflectionIndicator(situation))
    }

    if strain > 0.5 && rand.Float64() < strain * 0.6 {
        indicators = append(indicators, d.generateOverexplanationIndicator())
    }

    return indicators
}
        

10.5.3 Dialogue Integration

Indicators modify generated dialogue:

func (d *DeceptionEngine) ModifyDialogue(
    baseResponse string,
    indicators []DeceptionIndicator,
) string {

    modified := baseResponse

    for _, indicator := range indicators {
        switch indicator.Type {
        case Vagueness:
            modified = d.addVagueness(modified)
        case Deflection:
            modified = d.addDeflection(modified)
        case Overexplanation:
            modified = d.addOverexplanation(modified)
        }
    }

    return modified
}
        

10.6 Observer Analysis

10.6.1 Witness Processing

When players observe interactions, they form impressions:

type ObservationContext struct {
    Observer      PlayerID
    Subjects      []PlayerID
    Exchange      *ConversationExchange
    VisibleReactions []Reaction
}

func (o *ObserverEngine) ProcessObservation(ctx ObservationContext) *Observation {
    observation := &Observation{
        Observer:   ctx.Observer,
        Timestamp:  time.Now(),
        Subjects:   ctx.Subjects,
        Impressions: make(map[PlayerID]*Impression),
    }

    for _, subject := range ctx.Subjects {
        impression := o.analyzeSubject(subject, ctx.Exchange)
        observation.Impressions[subject] = impression
    }

    return observation
}
        

10.6.2 Reaction Observation

Observers watch for reactions:

type Reaction struct {
    Player      PlayerID
    TriggerEvent Event
    ReactionType ReactionType  // surprise, relief, guilt, etc.
    Intensity   float64
    Authenticity float64  // Observer's assessment of genuineness
}

func (o *ObserverEngine) AssessReactionAuthenticity(
    reaction Reaction,
    observer *PlayerState,
) float64 {

    // Observer's paranoia affects interpretation
    paranoiaFactor := observer.Emotion.Paranoia * 0.3

    // Relationship affects benefit of doubt
    trustFactor := observer.Emotion.TrustMap[reaction.Player] * 0.4

    // Reaction intensity affects credibility
    intensityFactor := 1.0 - abs(reaction.Intensity - 0.5) * 0.4

    return (intensityFactor + trustFactor - paranoiaFactor) / 2.0
}
        

10.6.3 Pattern Detection

Observers accumulate patterns:

type PatternDetector struct {
    Observer       PlayerID
    ObservationLog []*Observation
    DetectedPatterns []Pattern
}

func (pd *PatternDetector) DetectPatterns() []Pattern {
    patterns := []Pattern{}

    // Vote pattern detection
    votePatterns := pd.analyzeVoteHistory()
    patterns = append(patterns, votePatterns...)

    // Conversation pattern detection
    conversationPatterns := pd.analyzeConversationPatterns()
    patterns = append(patterns, conversationPatterns...)

    // Reaction pattern detection
    reactionPatterns := pd.analyzeReactionPatterns()
    patterns = append(patterns, reactionPatterns...)

    return patterns
}
        

10.7 Opinion Formation

10.7.1 Trust/Suspicion Updating

type OpinionUpdate struct {
    Player     PlayerID
    Target     PlayerID
    TrustDelta float64
    SuspicionDelta float64
    Reason     string
}

func (o *OpinionEngine) UpdateOpinion(
    player *PlayerState,
    target PlayerID,
    event Event,
) OpinionUpdate {

    update := OpinionUpdate{
        Player: player.ID,
        Target: target,
    }

    switch e := event.(type) {
    case *VoteEvent:
        update = o.processVoteImpact(player, target, e)
    case *AccusationEvent:
        update = o.processAccusationImpact(player, target, e)
    case *DefenseEvent:
        update = o.processDefenseImpact(player, target, e)
    case *MurderRevealEvent:
        update = o.processMurderImpact(player, target, e)
    }

    // Apply personality modifiers
    update = o.applyPersonalityModifiers(player, update)

    return update
}
        

10.7.2 Personality Modifier Effects

type Personality struct {
    TrustRate       float64  // How quickly trust forms
    SuspicionRate   float64  // How quickly suspicion forms
    ForgivenessFactor float64  // How quickly negative impressions fade
    ParanoiaBase    float64  // Baseline suspicion level
    ConfidenceBase  float64  // Baseline self-assurance
}

func (o *OpinionEngine) applyPersonalityModifiers(
    player *PlayerState,
    update OpinionUpdate,
) OpinionUpdate {

    personality := player.Personality

    // Paranoid personalities weigh suspicion more heavily
    if update.SuspicionDelta > 0 {
        update.SuspicionDelta *= (1.0 + personality.ParanoiaBase)
    }

    // Trusting personalities give more benefit of doubt
    if update.TrustDelta > 0 {
        update.TrustDelta *= (1.0 + personality.TrustRate)
    }

    return update
}
        

10.7.3 Conversation Summary Generation

After conversations, opinions update:

type ConversationSummary struct {
    Participants []PlayerID
    Topics       []Topic
    EmotionalTone float64
    TrustChanges  map[PlayerID]map[PlayerID]float64
    NewInformation []Information
    Suspicions    []Suspicion
}

func (o *OpinionEngine) GenerateSummary(
    conversation *Conversation,
) *ConversationSummary {

    summary := &ConversationSummary{
        Participants: conversation.Participants,
        TrustChanges: make(map[PlayerID]map[PlayerID]float64),
    }

    // Analyze each exchange
    for _, exchange := range conversation.Exchanges {
        summary.Topics = append(summary.Topics, exchange.Topic)
        summary.EmotionalTone += exchange.EmotionalIntensity
    }

    // Calculate relationship changes
    for _, participant := range conversation.Participants {
        summary.TrustChanges[participant] = o.calculateTrustChanges(
            participant, conversation,
        )
    }

    return summary
}
        

10.8 Role-Specific Behavior

Different roles require fundamentally different emotional baselines. This role-specific modelling aligns with the strategic archetypes framework, where each player type exhibits characteristic patterns of behaviour.

10.8.1 Traitor Deception Baseline

Traitors maintain constant low-level deception:

func (d *DeceptionEngine) InitializeTraitorState(player *PlayerState) {
    // Traitors always have role secret
    player.Secrets = append(player.Secrets, &Secret{
        Type:       RoleSecret,
        Content:    "Traitor",
        Importance: 1.0,  // Maximum importance
        Age:        0,
        ShareableWith: player.FellowTraitors,
        LeakRisk:   0.0,  // Initially low
    })

    // Initial masking required
    player.DeceptionState.DeceptionMode = Suppressing
    player.MaskingStrain.CurrentStrain = 0.1  // Baseline strain
}
        

10.8.2 Faithful Behavior Baseline

Faithfuls may deceive in limited ways:

func (d *DeceptionEngine) InitializeFaithfulState(player *PlayerState) {
    // Faithfuls may have alliance secrets
    // But no role secret to maintain

    player.DeceptionState.DeceptionMode = Authentic
    player.MaskingStrain.CurrentStrain = 0.0

    // Strategic deception possible but not constant
    player.DeceptionState.StrategyLevel = player.Personality.StrategicTendency
}
        

10.8.3 Recruited Traitor Transition

When Faithful becomes Traitor (a scenario explored in the Puppet Master Hypothesis and the Secret Traitor analysis):

func (d *DeceptionEngine) ProcessRecruitment(player *PlayerState) {
    // Add role secret
    player.Secrets = append(player.Secrets, &Secret{
        Type:       RoleSecret,
        Content:    "Recruited Traitor",
        Importance: 1.0,
        Age:        0,
        ShareableWith: player.FellowTraitors,
        LeakRisk:   0.1,  // Higher initial risk due to transition
    })

    // Shift to suppressing mode
    player.DeceptionState.DeceptionMode = Suppressing

    // Significant initial strain from transition
    player.MaskingStrain.CurrentStrain = 0.4

    // Guilt from former alliances
    player.Emotion.Guilt = 0.5
}
        

10.9 Integration with RAG Pipeline

10.9.1 Emotion-Aware Query Enhancement

func (r *RAGService) EnhanceQueryWithEmotion(
    query string,
    player *PlayerState,
) *EnhancedQuery {

    enhanced := &EnhancedQuery{
        BaseQuery: query,
        EmotionalContext: EmotionalContext{
            Dominant: player.Emotion.GetDominantEmotion(),
            Secondary: player.Emotion.GetSecondaryEmotion(),
            Strain: player.MaskingStrain.CurrentStrain,
        },
        DeceptionMode: player.DeceptionState.DeceptionMode,
    }

    return enhanced
}
        

10.9.2 Response Modification

func (r *RAGService) PostProcessResponse(
    response string,
    player *PlayerState,
) string {

    // Apply emotional coloring
    response = r.emotionEngine.ColorResponse(response, player.Emotion)

    // Apply deception modifications if Traitor or strategic
    if player.DeceptionState.DeceptionMode != Authentic {
        indicators := r.deceptionEngine.GenerateIndicators(player, response)
        response = r.deceptionEngine.ModifyDialogue(response, indicators)
    }

    return response
}
        

10.9.3 Prompt Integration

func (pb *PromptBuilder) AddEmotionalContext(emotion *EmotionState) {
    dominant := emotion.GetDominantEmotion()

    emotionalPrompt := fmt.Sprintf(
        "You are currently feeling primarily %s (intensity: %.2f).",
        dominant.Name, dominant.Intensity,
    )

    if emotion.GetSecondaryEmotion() != nil {
        secondary := emotion.GetSecondaryEmotion()
        emotionalPrompt += fmt.Sprintf(
            " You also feel %s (intensity: %.2f).",
            secondary.Name, secondary.Intensity,
        )
    }

    pb.components = append(pb.components, emotionalPrompt)
}
        

10.10 Conclusion: Authentic Deception

The emotion and deception engine provides the hidden architecture that makes AI behaviour meaningful:

  1. Internal State: Emotions that evolve based on events
  2. Display Control: Ability to show different from felt
  3. Strain Mechanics: Cost of maintaining deception
  4. Tell Generation: Observable indicators of strain
  5. Secret Management: Role-appropriate knowledge boundaries
  6. Opinion Formation: Dynamic relationship evolution

This system transforms simple dialogue generation into authentic social simulation, where what characters say reflects the complex interplay of strategy, emotion, and deception that defines The Traitors.

Chapter 11 builds on this foundation to create the complete simulation framework. The cognitive memory architecture further extends these concepts by adding persistent memory that allows AI agents to remember past deceptions, accumulated strain, and relationship history across the full game duration.

Thesis Contents