[![Gleam](https://img.shields.io/badge/Gleam-FFAFF3?style=for-the-badge&logo=gleam&logoColor=black)](https://gleam.run/) [![BEAM](https://img.shields.io/badge/BEAM-A90533?style=for-the-badge&logo=erlang&logoColor=white)](https://www.erlang.org/) [![OTP](https://img.shields.io/badge/OTP_26+-4B275F?style=for-the-badge)](https://www.erlang.org/doc/design_principles/des_princ) [![Hex](https://img.shields.io/badge/hex.pm-viva__emotion-A678DD?style=for-the-badge&logo=hex&logoColor=white)](https://hex.pm/packages/viva_emotion) [![PAD](https://img.shields.io/badge/PAD-3D_affect-EC4899?style=for-the-badge)](https://en.wikipedia.org/wiki/PAD_emotional_state_model) [![Tests](https://img.shields.io/badge/tests-55_passing-00875A?style=for-the-badge)](./test) [![Version](https://img.shields.io/badge/version-1.1.101-CD5C5C?style=for-the-badge)](./CHANGELOG.md) [![License](https://img.shields.io/badge/license-MIT-228B22?style=for-the-badge)](./LICENSE) --- *"Joy is a vector. Fear is a fold. Mood is the average of the soul."*

[!IMPORTANT] 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.

Property Value
Language Pure Gleam (type-safe functional)
Runtime BEAM / OTP 26+
Built onviva_math β‰₯ 1.2.100
Tests 55 passing
Stimuli 14 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 | Tool | Version | Required for | | :---------- | :-------- | :--------------- | | Gleam | `>= 1.14` | Build / runtime | | Erlang/OTP | `>= 26` | BEAM 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 | Module | Purpose | | :----------------------- | :----------------------------------------------------------------- | | `viva_emotion` | Top-level state machine: `new`, `feel`, `tick`, `classify`, `fuse` | | `viva_emotion/pad` | PAD `Vec3` constructors + ops (re-exports from `viva_math/vector`) | | `viva_emotion/stimulus` | 14 exhaustive stimulus types + per-type PAD weights | | `viva_emotion/emotion` | Discrete emotion classes (Joy, Fear, …) + `ClassificationConfig` | | `viva_emotion/dynamics` | O-U mean reversion + Cusp jump triggers + `DynamicsConfig` | | `viva_emotion/fusion` | Need / Past / Personality fusion with adaptive weighting | | `viva_emotion/mood` | Long-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

Principle Description
Exhaustive stimuli Compile-time pattern coverage; missing a stimulus is a build error
Deterministic by defaulttick is pure; entropy only via explicit tick_with_noise
Tunable personality Every dynamics + classification parameter is overridable
No silent failures All ops typed; no string-keyed emotion maps
viva_math grounded Cusp, 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

Phase Status
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

Package Purpose
viva_math Mathematical foundations
viva_emotionPAD emotional dynamics (this package)
viva_tensor FP8 LLM inference on the BEAM
viva_telemetry Observability suite
viva_aion Time perception
viva_glyph Symbolic language

**Star if BEAM should know how it feels ⭐** [![GitHub stars](https://img.shields.io/github/stars/gabrielmaialva33/viva_emotion?style=social)](https://github.com/gabrielmaialva33/viva_emotion) *Created by Gabriel Maia · MIT License*