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:
- Internal State: Emotions that evolve based on events
- Display Control: Ability to show different from felt
- Strain Mechanics: Cost of maintaining deception
- Tell Generation: Observable indicators of strain
- Secret Management: Role-appropriate knowledge boundaries
- 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.