Gleam BEAM OTP Hex PAD Tests Version License


“Joy is a vector. Fear is a fold. Mood is the average of the soul.”


viva_emotion IS NOT A SENTIMENT CLASSIFIER. It is a dynamical-systems emotional core — PAD state, Ornstein-Uhlenbeck mean-reversion, Cusp catastrophe jumps, multi-source fusion, EMA-tracked long-term mood, and a “Big Bounce” continuity model across death/rebirth cycles.

Built on top of viva_math, with compile-time exhaustive pattern matching on every stimulus.


🎯 Overview

The emotional core of VIVA — a sentient digital life research project. Emotions are modeled as a dynamical system in 3D PAD space, evolving via O-U dynamics, jumping via Cusp catastrophe, fusing across Need/Past/ Personality sources, and accumulating into long-term Mood via EMA.

The API is small, type-safe, and deliberately deterministic: same inputs produce the same trajectory unless you inject external entropy yourself.

PropertyValue
LanguagePure Gleam (type-safe functional)
RuntimeBEAM / OTP 26+
Built onviva_math ≥ 1.2.100
Tests55 passing
Stimuli14 exhaustive types (compile-time checked)
Public APIviva_emotion/{pad,emotion,stimulus,dynamics,fusion,mood}

⚡ Quick Start

gleam add viva_emotion@1
import viva_emotion
import viva_emotion/emotion
import viva_emotion/stimulus

pub fn main() {
  let state = viva_emotion.new()

  // Feel a stimulus
  let state = viva_emotion.feel(state, stimulus.Success, 1.0)

  // Evolve in time (O-U + maybe Cusp jump)
  let #(state, jumped) = viva_emotion.tick(state, 0.1)

  // Classify nearest discrete emotion
  let classified = viva_emotion.classify(state)
  // classified.emotion == emotion.Joy
}
📋 Prerequisites
ToolVersionRequired for
Gleam>= 1.14Build / runtime
Erlang/OTP>= 26BEAM target

Zero NIFs. Zero C dependencies. Pure functional.


🏗️ Architecture

   ┌──────────────────────────────────────────────────────────┐
   │                Gleam application code                    │
   │              viva_emotion.{new,feel,tick,...}            │
   └────────┬─────────────────────────────────────────────────┘
            │
   ┌────────▼─────────────────────────────────────────────────┐
   │                  viva_emotion modules                    │
   │                                                          │
   │   ┌─────────┐  ┌────────────┐  ┌──────────┐  ┌────────┐  │
   │   │   pad   │  │  stimulus  │  │ dynamics │  │emotion │  │
   │   │  Vec3   │  │ 14 types,  │  │  O-U     │  │ 8 base │  │
   │   │  PAD ℝ³ │  │ exhaustive │  │  Cusp    │  │ classes│  │
   │   │   ops   │  │  weights   │  │  config  │  │ + 4 ex.│  │
   │   └─────────┘  └────────────┘  └──────────┘  └────────┘  │
   │                                                          │
   │            ┌────────────┐         ┌────────┐             │
   │            │   fusion   │         │  mood  │             │
   │            │ Need·Past· │         │  EMA   │             │
   │            │Personality │         │ Big    │             │
   │            │  adaptive  │         │ Bounce │             │
   │            └────────────┘         └────────┘             │
   └────────┬─────────────────────────────────────────────────┘
            │
   ┌────────▼─────────────────────────────────────────────────┐
   │       viva_math (cusp, free_energy, attractor, …)        │
   └──────────────────────────────────────────────────────────┘
📋 Core modules
ModulePurpose
viva_emotionTop-level state machine: new, feel, tick, classify, fuse
viva_emotion/padPAD Vec3 constructors + ops (re-exports from viva_math/vector)
viva_emotion/stimulus14 exhaustive stimulus types + per-type PAD weights
viva_emotion/emotionDiscrete emotion classes (Joy, Fear, …) + ClassificationConfig
viva_emotion/dynamicsO-U mean reversion + Cusp jump triggers + DynamicsConfig
viva_emotion/fusionNeed / Past / Personality fusion with adaptive weighting
viva_emotion/moodLong-term mood via EMA + Big Bounce continuity

🧬 Theoretical Background

PAD Model — Mehrabian (1996)

Every emotion lives as a point in 3D vector space — Pleasure, Arousal, Dominance — each axis in [-1, 1].

Ornstein-Uhlenbeck dynamics

Emotion drifts back to a baseline with rate θ, modulated by a stochastic term σ dW_t. By default the noise term is zero (deterministic replay); inject external entropy via tick_with_noise for organic evolution.

Cusp catastrophe — Thom (1972)

Under high arousal and the right dominance trigger, the system becomes bistable: a small perturbation flips Pleasure sign discretely. This models “the moment everything snaps.”

Fusion — Borotschnig (2025)

Three emotional sources are blended with context-aware weights:

Weights adapt to current arousal, memory confidence, and situation novelty.

Long-term mood via EMA

Per-axis Exponential Moving Average delegated to viva_math/statistics. Optional Big Bounce: when an agent “dies,” a configurable decay multiplier preserves part of its mood into its successor — emotional continuity across rebirth.


🎨 Design Principles

PrincipleDescription
Exhaustive stimuliCompile-time pattern coverage; missing a stimulus is a build error
Deterministic by defaulttick is pure; entropy only via explicit tick_with_noise
Tunable personalityEvery dynamics + classification parameter is overridable
No silent failuresAll ops typed; no string-keyed emotion maps
viva_math groundedCusp, attractors, EMA all delegate to upstream math

📚 Public API Highlights

Personality configuration

import viva_emotion
import viva_emotion/dynamics
import viva_emotion/emotion
import viva_emotion/stimulus
import viva_emotion/pad

// Anxious — easier to enter existential states
let anxious_classification =
  emotion.ClassificationConfig(
    high_threshold: 0.2,
    low_threshold: -0.2,
    existential_arousal: 0.5,
    existential_pleasure: -0.2,
  )

// Stoic — rejection hurts less
let stoic_weights =
  stimulus.default_weights()
  |> stimulus.weight(stimulus.Rejection, pad.new(-0.1, 0.1, 0.0))

// Volatile — easier Cusp jumps
let volatile_dynamics =
  dynamics.DynamicsConfig(
    ..dynamics.default_config(),
    cusp_dominance_trigger: -0.1,
    cusp_flip_pleasure_damp: 0.95,
  )

let anxious_viva =
  viva_emotion.with_full_config(
    pad.new(-0.1, 0.2, -0.1),
    volatile_dynamics,
    anxious_classification,
    stoic_weights,
  )

Emotion fusion

import viva_emotion
import viva_emotion/fusion
import viva_emotion/pad

let need = pad.new(-0.3, 0.4, -0.2)
let past = pad.new(0.2, 0.1, 0.3)

let context =
  fusion.FusionContext(
    arousal: 0.4,
    confidence: 0.7,
    novelty: 0.3,
  )

let state =
  viva_emotion.new()
  |> viva_emotion.fuse(need, past, context)

let conflict  = viva_emotion.has_emotional_conflict(state, need, past)
let coherence = viva_emotion.emotional_coherence(state, need, past)

Long-term mood + Big Bounce

import viva_emotion
import viva_emotion/mood
import viva_emotion/stimulus

let state = viva_emotion.new_with_mood()

let state =
  viva_emotion.EmotionalStateWithMood(
    ..state,
    emotion: viva_emotion.feel(state.emotion, stimulus.Success, 1.0),
  )

let state = viva_emotion.update_mood(state)

let is_happy = viva_emotion.mood_is_positive(state)
let valence  = viva_emotion.mood_valence(state)

// Big Bounce: carry 80% of mood across death/rebirth
let reborn = viva_emotion.bounce_mood(state, 0.8)

Stochastic mode

// External entropy (Rust RNG, hardware noise, etc.)
let noise = pad.new(random_p, random_a, random_d)
let #(state, jumped) = viva_emotion.tick_with_noise(state, 0.1, noise)

// Deterministic replay
let #(state, jumped) = viva_emotion.tick_with_noise(state, 0.1, pad.neutral())

🗺️ Roadmap

PhaseStatus
PAD vector type + ops
14-stimulus exhaustive pattern matching
Ornstein-Uhlenbeck mean reversion
Cusp catastrophe jumps
8 base emotions + 4 existential states
Need / Past / Personality fusion with adaptive weights
Conflict + coherence metrics
EMA long-term mood (delegated to viva_math)
Big Bounce mood continuity
Stochastic mode (tick_with_noise)
Variational free-energy classifier
Mood-conditional Cusp triggers
Multi-agent emotional contagion
Property-based tests on every dynamics path

🤝 Contributing

git checkout -b feature/your-feature
gleam test                # 55 tests
gleam format --check src test
gleam build

See CONTRIBUTING.md for guidelines · SECURITY for vulnerability reporting.


📖 References


🌌 VIVA Ecosystem

PackagePurpose
viva_mathMathematical foundations
viva_emotionPAD emotional dynamics (this package)
viva_tensorFP8 LLM inference on the BEAM
viva_telemetryObservability suite
viva_aionTime perception
viva_glyphSymbolic language

Star if BEAM should know how it feels ⭐

GitHub stars

Created by Gabriel Maia · MIT License

Search Document