Skip to main content

README.md

<div align="center">

<img src="https://capsule-render.vercel.app/api?type=waving&color=0:EC4899,100:1A1A1A&height=200&section=header&text=viva_emotion&fontSize=64&fontColor=fff&animation=twinkling&fontAlignY=35&desc=Type-safe%20emotional%20core%20for%20digital%20consciousness&descSize=18&descAlignY=55" width="100%"/>

[![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."*

</div>

---

> [!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`](https://hex.pm/packages/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 on**        | [`viva_math`](https://hex.pm/packages/viva_math) β‰₯ 1.2.100 |
| **Tests**           | 55 passing                                           |
| **Stimuli**         | 14 exhaustive types (compile-time checked)           |
| **Public API**      | `viva_emotion/{pad,emotion,stimulus,dynamics,fusion,mood}` |

---

## ⚑ Quick Start

```sh
gleam add viva_emotion@1
```

```gleam
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
}
```

<details>
<summary><strong>πŸ“‹ Prerequisites</strong></summary>

| Tool        | Version   | Required for     |
| :---------- | :-------- | :--------------- |
| Gleam       | `>= 1.14` | Build / runtime  |
| Erlang/OTP  | `>= 26`   | BEAM target      |

Zero NIFs. Zero C dependencies. Pure functional.

</details>

---

## πŸ—οΈ 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, …)        β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

<details>
<summary><strong>πŸ“‹ Core modules</strong></summary>

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

</details>

---

## 🧬 Theoretical Background

### PAD Model β€” Mehrabian (1996)

Every emotion lives as a point in 3D vector space β€” **P**leasure,
**A**rousal, **D**ominance β€” 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:

- **Need** (interoception / hardware signals)
- **Past** (memory retrieval confidence)
- **Personality** (baseline temperament)

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 default**         | `tick` 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

```gleam
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

```gleam
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

```gleam
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

```gleam
// 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

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

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines Β· [SECURITY](SECURITY.md)
for vulnerability reporting.

---

## πŸ“– References

- Mehrabian (1996) β€” *Pleasure-arousal-dominance: A general framework*
- Thom (1972) β€” *Structural Stability and Morphogenesis*
- Borotschnig (2025) β€” *Emotional fusion under uncertainty*
- Oravecz et al. (2009) β€” *Ornstein-Uhlenbeck Process in Affective Dynamics*
- Friston (2010) β€” *The free-energy principle: a unified brain theory?*

---

## 🌌 VIVA Ecosystem

| Package              | Purpose                                       |
| :------------------- | :-------------------------------------------- |
| `viva_math`          | Mathematical foundations                      |
| **`viva_emotion`**   | **PAD emotional dynamics (this package)**     |
| `viva_tensor`        | FP8 LLM inference on the BEAM                 |
| `viva_telemetry`     | Observability suite                           |
| `viva_aion`          | Time perception                               |
| `viva_glyph`         | Symbolic language                             |

---

<div align="center">

**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*

<img src="https://capsule-render.vercel.app/api?type=waving&color=0:1A1A1A,100:EC4899&height=100&section=footer" width="100%"/>

</div>