<div align="center">
<img src="https://capsule-render.vercel.app/api?type=waving&color=0:EC4899,100:1A1A1A&height=200§ion=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%"/>
[](https://gleam.run/)
[](https://www.erlang.org/)
[](https://www.erlang.org/doc/design_principles/des_princ)
[](https://hex.pm/packages/viva_emotion)
[](https://en.wikipedia.org/wiki/PAD_emotional_state_model)
[](./test)
[](./CHANGELOG.md)
[](./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 β**
[](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§ion=footer" width="100%"/>
</div>