Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

⌈ EMBODIED ⌋ — The Terminal as Body

Terminal PRD · v4.10

“The interface is the Golem’s nervous system”

Source: engagement-prd/bardo-v4-10-embodied-consciousness.md Cross-references: ../../18-interfaces/rendering/00-design-system.md (ROSEDUST visual identity spec for color, typography, and atmospheric rendering), ../screens/03-interaction-hierarchy.md (29-screen, 6-window navigation model and persistent chrome), ../screens/00-screen-catalog.md (29-screen summary across 6 windows)

Reader orientation: This document specifies how the terminal itself embodies the Golem’s cognitive and emotional state. It belongs to the interfaces/perspective layer and maps the terminal’s five zones (HEAD, CHEST, GUT, LIMB, GROUND) to a somatic body metaphor where borders are bones, color shifts are flushes, and character distortions are flinches. Key concepts: Golem (a mortal autonomous DeFi agent), PAD vector (Pleasure-Arousal-Dominance emotional coordinates from the Daimon subsystem), CorticalState (the Golem’s 32-signal atomic perception surface), and BehavioralPhase (the Golem’s lifecycle stage: Thriving / Stable / Conservation / Declining / Terminal). For unfamiliar terms, see prd2/shared/glossary.md.


0. What This Is

The Golem does not have a body. The terminal IS its body. Every border is a bone. Every color shift is a flush. Every character distortion is a flinch. This document specifies how the terminal’s structural elements embody the Golem’s cognitive and emotional state at every moment.

This is not metaphor. It is the implementation of Law 6 (The Terminal IS the Body) from the ROSEDUST design system, applied systematically to every visual property the renderer controls.


1. The Somatic Architecture

Five Terminal Body Zones

┌──────────────────────────────────────────────────┐
│              HEAD ZONE (top 3 rows)               │
│   Conscious decisions, labels, phase indicators   │
│   Responds to: deliberation, Φ score, regime      │
├──────────────────────────────────────────────────┤
│                                                    │
│              CHEST ZONE (upper center)             │
│   Emotional core, heartbeat rhythm, PAD center     │
│   Responds to: PAD shifts, arousal, somatic pulse  │
│                                                    │
│   ┌─────────────┐  ┌──────────────────────────┐   │
│   │   SPECTRE    │  │    CONTENT AREA          │   │
│   │  (left)      │  │                          │   │
│   │              │  │    GUT ZONE              │   │
│   │  Intero-     │  │    (lower content)       │   │
│   │  ception     │  │    Pre-conscious markers │   │
│   │  center      │  │    Responds to: somatic   │   │
│   │              │  │    markers, intuition     │   │
│   └─────────────┘  └──────────────────────────┘   │
│                                                    │
│              LIMB ZONE (borders, margins)          │
│   Action readiness, tension, structural integrity  │
│   Responds to: dominance, phase, mortality clocks  │
├──────────────────────────────────────────────────┤
│              GROUND ZONE (bottom 2 rows)          │
│   Stability, balance, connection to context        │
│   Responds to: overall vitality, economic clock    │
└──────────────────────────────────────────────────┘

Somatic Marker Events

The critical constraint: somatic response MUST be visible 200-500ms BEFORE the decision text that follows it.

Negative markers (warning/danger):

PhaseTimeVisual
Pre-signalt=0msGut zone: 3-5 chars shift via wave substitution (─ → ∿ → ≈ → ∿ → ─)
Emotional riset=200msChest zone: color temperature shifts cool (rose → rose_bright)
Body tensiont=400msLimb zone: borders tighten (│ → ┃ → ║), margin shrinks 1 col
Conscious awarenesst=600msHead zone: first word of decision text renders
Full deliberationt=800ms+Content renders normally, somatic disturbance fading
Resolutiont=1500msAll zones settle (if resolved) or persist (if unresolved)

Positive markers (opportunity/confidence):

PhaseTimeVisual
Pre-signalt=0msGut zone: characters brighten 10-15% in lower third
Emotional riset=200msChest zone: color shifts warm (rose → bone_dim)
Body expansiont=400msLimb zone: borders ease (║ → │ → ┊), margins widen 1 col
Conscious awarenesst=600msHead zone: first word of decision text renders

Startle markers (unexpected events):

PhaseTimeVisual
Impactt=0msALL zones: single-frame content displacement (everything shifts ±1 cell)
Freezet=100msALL zones: 100ms of complete stillness (no shimmer, no breathing, no animation)
Recoveryt=200msZones resume normal animation, starting center outward
Processingt=500ms+Normal rendering resumes, possibly with heightened arousal effects

The 100ms stillness before recovery increases perceived weight of the event by ~30% (game-feel research).

The Heartbeat as Master Clock

Heartbeat period = 4.0 - (arousal × 2.0) seconds
  A = -1.0: 6s period (dormant)
  A =  0.0: 4s period (resting)
  A = +0.5: 3s period (alert)
  A = +1.0: 2s period (racing)
  A > +0.7 with noise: arrhythmic (period ± 20% random per cycle)

Synchronized to heartbeat:

  • Screen brightness: ±3% sine pulse on heartbeat frequency
  • Border animation: box-drawing chars cycle thickness on heartbeat
  • Shimmer rate: all particle systems pulse with heartbeat
  • Text rendering speed (Portal mode): new text appears in heartbeat rhythm — each beat releases the next chunk

Racing heartbeat → urgent terminal. Slow → languid. Arrhythmic (Terminal phase) → unpredictable.


2. PAD-Driven Interface Transformation

The Color Engine

Every frame, the renderer reads the Golem’s interpolated PAD vector and computes target color properties:

Target brightness = 0.69 × Pleasure + base_brightness
Target saturation = 0.60 × Arousal + base_saturation
Target hue_shift  = map(Pleasure, Dominance) → ROSEDUST hue offset

Pleasure Axis (P: -1 to +1)

P = -1.0: bg_void shifts to #080606 (warm-black, Texhnolyze register)
          rose shifts toward rose_bright (danger coloring on everything)
          text dims 15% globally
          all borders thin to minimum weight

P =  0.0: standard ROSEDUST palette

P = +1.0: bg_void shifts to #06060A (cooler, calmer void)
          rose shifts toward bone_dim (warm confidence)
          text brightens 10% globally
          borders at full weight

Arousal Axis (A: -1 to +1)

A = -1.0: shimmer rate halved
          text rendering speed reduced
          all transitions 2× normal duration
          noise floor drops to 0.1%
          near-stillness — the terminal is barely breathing

A =  0.0: standard animation rates

A = +1.0: shimmer rate doubled
          text at max speed
          transitions snap (0.5× duration)
          noise floor rises to 0.5%
          data rain activates
          scanline intensity: 0.08 → 0.16
          chromatic aberration on bright elements (offset ±1 col in rose/dream)

Dominance Axis (D: -1 to +1)

D = -1.0: borders become dashed (┌ → ╌), fragmentary
          text occasionally renders with alternatives: "stable|volatile"
          layout shifts 2-3 cols from optimal position
          line spacing becomes uneven
          the Golem is uncertain — structure reflects this

D =  0.0: standard layout

D = +1.0: borders double-line (┌ → ╔), commanding
          letter-spacing increases
          layout locks to precise grid alignment
          all elements feel anchored, authoritative

Composite Emotional States

Joy (+P +A +D): Warm palette, fast confident animations, ascending particle direction, cohered Spectre, double-line borders, bright clean text. Heartbeat steady at 90 BPM equivalent. The terminal feels alive, assured, capable.

Fear (-P +A -D): Cool palette + rose_bright danger tints, jittery fast animations, character corruption at edges, chromatic aberration on bright elements, dashed flickering borders, text that renders slightly too fast (rushing, not confident). Heartbeat racing and slightly irregular. The terminal feels like it’s flinching.

Anger (-P +A +D): Rose_bright everywhere, aggressive fast animations, content pushes outward (wider margins → narrower margins, text expands), double-line borders in rose_bright, staccato bursts. The terminal is clenching.

Sadness (-P -A -D): Desaturated palette (all rose variants shifted toward grey), slow heavy animations, sinking particle direction, content drifts toward bottom, degraded thin borders, text dims and trails off (long sentences left incomplete…). Heartbeat at 50 BPM, heavy. The terminal is exhausted.

Anticipation (+P -A +D): Warm but restrained, steady measured animations, scanning horizontal sweeps in shimmer, sharp borders, precisely formatted text. A held breath.

Surprise (-P +A -D): Brief bone spike on all elements (200ms), sudden 100ms stillness, then rapid recovery with widened borders and expanded spacing. Eyes → ◎ ◎. Text renders with gaps (the → t h e). The terminal felt a shock.

Disgust (-P -A +D): Muted palette, near-stillness, rigid grid that feels oppressive rather than orderly, minimal text with maximum spacing. The terminal has withdrawn.

Trust (+P +A -D): Stable warm palette, smooth even animations, consistent steady rhythm, borders constant (no flicker, no thinning), text at comfortable reading pace. Calm and open.


3. The MAGI Theater

Standard Mode (Third-Person)

Three columns with names, verdicts, and activity indicators. Clean, informational, detached.

Portal Mode (First-Person): The Triptych

Three voices become competing presences in cognitive space. Regions expand/contract based on which voice is dominant:

Unanimous agreement:  All three merge into single centered column.
                      Borders between panels dissolve.
                      Text in unified color.

Split (2-1):          Majority shares 65% of width.
                      Dissenter gets 35%.
                      Bright border between them — disagreement visible.
                      Dissenter's text dimmer but readable.

Three-way split:      Equal thirds.
                      Double borders between all panels.
                      Each voice at its own color intensity.
                      The Golem is confused — the layout reflects indecision.

Voice Characters

VoiceRoleColorTypographyPersonality
MELCHIORScientist/Analystdream (indigo)Precise, tabular, numbers-heavy“P(success) = 0.73. Expected value: +$14.20. Confidence interval: ±$6.80.”
BALTHASARMother/Protectorwarning (amber)Cautious, question-heavy, conditional“But what if gas spikes? Remember episode #347. The Golem was overexposed.”
CASPERWoman/IntuitionroseFlowing, associative, feeling-adjacent“Something about this pool feels like the one that paid off in week 3. Not the numbers — the shape of the opportunity.”

Passive Interjections

During the 9-step decision pipeline, MAGI voices inject commentary at the margins of rendering content (Disco Elysium pattern):

Normal pipeline text flowing here about market conditions
and probe results indicating a potential opportunity in the
   BALTHASAR: ⚠ last time arousal was this high we lost 3.4%
WETH/USDC 0.05% pool. The current spread suggests—
                                       CASPER: ...feels right though
—an entry point within acceptable risk parameters.
   MELCHIOR: EV positive at 1.2σ. Proceed.

Interjection frequency scales with decision importance: T0 = none (voices silent during routine). T1 = 0-1 (brief comment). T2 = 3-6 (active debate). T3 = near-continuous (the voices are shouting).

Overruled voices persist as ghosts. Outvoted text dims to text_ghost and remains visible at the margin. The losing argument haunts the decision. If the decision later turns out badly, ghost text brightens briefly — “I told you so” as phosphor residue.


4. Mortality as Felt Degradation

Three Clocks as Interface Properties

Each mortality clock degrades a different aspect of the terminal’s structural integrity. The degradation is continuous, not stepped — every frame, current clock values modulate their respective visual properties.

Economic Clock → Display Space

Economic = 1.0: Full display area. Standard margins (1 col each side).
Economic = 0.7: Margins grow to 2 cols. Content area shrinks.
Economic = 0.5: Margins grow to 4 cols. Some panes stack vertically.
Economic = 0.3: Margins grow to 6 cols. Only most important pane full size.
Economic = 0.1: Margins at 10+ cols. Content is a narrow strip.
                The Golem can barely afford to show you anything.
                Margins fill with: $ signs in text_phantom, credit digits fading.

Epistemic Clock → Text Integrity

Epistemic = 1.0: All text clean. Full Unicode. Clear typography.
Epistemic = 0.7: 1 in 200 chars flickers per frame.
Epistemic = 0.5: 1 in 80 chars → ░. Words render with alternatives: "stable|volatile"
Epistemic = 0.3: 1 in 30 chars corrupted. Words break mid-render.
                  Grimoire references show as "???" — the Golem can't recall.
Epistemic = 0.1: Heavy corruption. Only high-confidence short text survives.
                  The Golem is losing its mind.

Stochastic Clock → Structural Stability

Stochastic = 1.0: No disruptions. Clean, predictable display.
Stochastic = 0.7: Rare glitches (1 per minute). Brief single-frame disruptions.
Stochastic = 0.5: Moderate glitches (1 per 15s). Full-line corruption, 2-3 frames.
Stochastic = 0.3: Frequent glitches (1 per 5s). Multi-line, borders occasionally break.
Stochastic = 0.1: Near-constant disruption. Screen shake. Random content replacement.
                   Cannot trust what you see.

Cross-Contamination

Below vitality 0.3 (Declining phase), clocks begin cross-contaminating:

  • Economic decay → text corruption (can’t afford clear rendering)
  • Epistemic decay → structural instability (uncertain knowledge = uncertain borders)
  • Stochastic disruption → economic indicators glitch (can’t assess own resources)

Below vitality 0.1 (Terminal phase), all three compound simultaneously. The interface is failing across every dimension.

The Corridor (Portal Mode, Fate > Mortality)

The display transforms into a corridor:

  • Floor: economic clock (tiles dissolving into then void)
  • Walls: epistemic clock (text on walls fading, becoming unreadable as they recede)
  • Ceiling: stochastic clock (random chunks dropping — descending one row)
  • End of corridor: projected death — a door you approach but never reach
  • As vitality drops, corridor shortens — door gets closer
  • Styx river visible through eroding floor tiles. Knowledge from the dead drifts in the current.

5. Rust Implementation

Performance Budget

All embodied consciousness effects must consume <2ms of the 16.6ms frame budget (60fps). Effects are character-level buffer operations — achievable.

EffectImplementationCost
PAD color interpolationComputed once per frame, applied as color modifierO(1) compute + O(cells) apply
Somatic marker eventsPre-computed distortion patterns as post-processing passSame as tachyonfx shaders
Heartbeat syncSingle sine-wave computationNegligible
MAGI interjectionsText insertion into existing render flowProportional to text length
Mortality degradationPost-processing passes (substitution, margin adjustment, glitch)O(affected_cells)

State Coupling

The embodied system reads from the same 26+ interpolating variables as the standard render loop. No additional state required:

  • PAD vector (3 floats) → color engine, typography, spacing
  • Heartbeat phase (1 float) → master clock
  • Somatic markers (event stream) → zone disturbances
  • MAGI state (3 vote states + confidence) → triptych layout
  • Three mortality clocks (3 floats) → space/text/stability degradation
  • Behavioral phase (enum) → intensity scaling

All interpolation happens at existing variable lerp rates (fast/medium/slow/glacial). The embodied system is a pure RENDERER of existing state, not a new state system.

EmbodiedState

#![allow(unused)]
fn main() {
pub struct EmbodiedState {
    pub pad: PadVector,
    pub pad_target: PadVector,
    pub heartbeat_phase: f64,       // 0..TAU, current phase in heartbeat cycle
    pub heartbeat_period: f64,      // seconds per beat, derived from arousal
    pub somatic_markers: Vec<SomaticMarker>,
    pub magi: MagiState,
    pub mortality: MortalityState,
    pub phase: BehavioralPhase,
}

pub struct SomaticMarker {
    pub id: u64,
    pub valence: f64,               // -1.0 (danger) to +1.0 (confidence)
    pub strength: f64,              // 0.0-1.0
    pub fired_at: std::time::Instant,
    pub resolved: bool,
}

pub struct MagiState {
    pub melchior: MagiVote,
    pub balthasar: MagiVote,
    pub casper: MagiVote,
    pub last_interjections: Vec<MagiInterjection>,
}

pub struct MagiVote {
    pub verdict: Option<bool>,
    pub confidence: f64,
    pub text: Option<String>,
}

pub struct MagiInterjection {
    pub voice: MagiVoice,
    pub text: String,
    pub position: u16,
    pub age_frames: usize,
}

pub enum MagiVoice { Melchior, Balthasar, Casper }

pub struct MortalityState {
    pub economic: f64,
    pub epistemic: f64,
    pub stochastic: f64,
}

pub enum BehavioralPhase {
    Thriving, Stable, Conservation, Declining, Terminal,
}
}

PadVector

#![allow(unused)]
fn main() {
#[derive(Clone, Copy)]
pub struct PadVector {
    pub pleasure: f64,      // -1.0..1.0
    pub arousal: f64,       // -1.0..1.0
    pub dominance: f64,     // -1.0..1.0
}

impl PadVector {
    pub fn lerp(&self, target: &PadVector, t: f64) -> PadVector {
        PadVector {
            pleasure:  self.pleasure  + (target.pleasure  - self.pleasure)  * t,
            arousal:   self.arousal   + (target.arousal   - self.arousal)   * t,
            dominance: self.dominance + (target.dominance - self.dominance) * t,
        }
    }

    pub fn heartbeat_period(&self) -> f64 {
        (4.0 - self.arousal * 2.0).clamp(2.0, 6.0)
    }

    pub fn border_weight(&self) -> BorderWeight {
        if self.dominance > 0.3 { BorderWeight::Double }
        else if self.dominance < -0.3 { BorderWeight::Dashed }
        else { BorderWeight::Single }
    }

    pub fn text_brightness_modifier(&self) -> f64 {
        1.0 + self.pleasure * 0.1   // ±10% based on pleasure
    }

    pub fn transition_speed_modifier(&self) -> f64 {
        if self.arousal > 0.5 { 0.5 }          // snap fast
        else if self.arousal < -0.5 { 2.0 }    // slow and heavy
        else { 1.0 }
    }
}

pub enum BorderWeight { Single, Double, Dashed }
}

Heartbeat Oscillator

#![allow(unused)]
fn main() {
pub struct HeartbeatOscillator {
    phase: f64,
    period: f64,
    is_arrhythmic: bool,
    rng: fastrand::Rng,
}

impl HeartbeatOscillator {
    pub fn tick(&mut self, dt: f64, pad: &PadVector) {
        self.period = pad.heartbeat_period();
        if pad.arousal > 0.7 {
            self.is_arrhythmic = true;
            let jitter = (self.rng.f64() - 0.5) * self.period * 0.2;
            self.phase += (dt / (self.period + jitter)) * std::f64::consts::TAU;
        } else {
            self.is_arrhythmic = false;
            self.phase += (dt / self.period) * std::f64::consts::TAU;
        }
        self.phase %= std::f64::consts::TAU;
    }

    pub fn brightness_pulse(&self) -> f64 {
        0.97 + 0.03 * self.phase.sin()     // ±3% brightness
    }

    pub fn shimmer_rate(&self, pad: &PadVector) -> f64 {
        let base = 1.0;
        let arousal_mod = 1.0 + pad.arousal.clamp(-0.5, 1.0);
        base * arousal_mod
    }
}
}

Mortality Post-Processing

#![allow(unused)]
fn main() {
pub fn apply_mortality_effects(
    area: Rect, buf: &mut Buffer,
    mortality: &MortalityState,
    rng: &mut fastrand::Rng,
) {
    // Economic: shrink margins (handled at layout level via Zone::from_terminal)
    if mortality.economic < 0.7 {
        let extra_cols = ((0.7 - mortality.economic) / 0.6 * 10.0) as u16;
        for y in area.top()..area.bottom() {
            for col in 0..extra_cols {
                let ch = if rng.f64() < 0.05 { '$' } else { ' ' };
                buf[(area.x + col, y)].set_char(ch)
                    .set_fg(Color::Rgb(24, 20, 28)); // text_phantom
                buf[(area.right() - 1 - col, y)].set_char(ch)
                    .set_fg(Color::Rgb(24, 20, 28));
            }
        }
    }

    // Epistemic: text corruption
    if mortality.epistemic < 0.7 {
        let corruption_rate = (0.7 - mortality.epistemic) / 0.7 * 0.1;
        let corruption_chars = ['░', '▒', '▓', '?', '⌀'];
        for y in area.top()..area.bottom() {
            for x in area.left()..area.right() {
                if rng.f64() < corruption_rate {
                    let cell = &mut buf[(x, y)];
                    if cell.symbol() != " " {
                        let ch = corruption_chars[rng.usize(0..corruption_chars.len())];
                        cell.set_char(ch).set_fg(Color::Rgb(80, 72, 84));
                    }
                }
            }
        }
    }

    // Stochastic: random glitch events
    if mortality.stochastic < 0.7 {
        let glitch_prob_per_frame = (0.7 - mortality.stochastic) / 0.7 * 0.05;
        if rng.f64() < glitch_prob_per_frame {
            let y = rng.u16(area.top()..area.bottom());
            for x in area.left()..area.right() {
                if rng.f64() < 0.6 {
                    let v = rng.u8(40..120);
                    buf[(x, y)].set_char('█').set_fg(Color::Rgb(v, v/2, v));
                }
            }
        }
    }
}
}

⌈ the terminal does not show you the golem’s body. the terminal IS the golem’s body. ⌋ ║▒░ BARDO ░▒║