Skip to main content

proof_engine/entity/
mod.rs

1//! Amorphous entity rendering.
2//!
3//! Entities are not rigid sprites. They are clusters of Glyphs bound together
4//! by internal force fields. Their visual form is emergent from the binding forces.
5//! As HP decreases, cohesion drops and the entity visibly falls apart.
6
7pub mod formation;
8pub mod cohesion;
9pub mod ai;
10
11use crate::glyph::GlyphId;
12use crate::math::ForceField;
13use glam::{Vec3, Vec4};
14
15/// Opaque handle to an entity in the scene.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct EntityId(pub u32);
18
19/// An amorphous visual entity held together by binding forces.
20#[derive(Clone)]
21pub struct AmorphousEntity {
22    // ── Identity ─────────────────────────────────────────────────────────────
23    pub name: String,
24    pub position: Vec3,
25
26    // ── Binding ──────────────────────────────────────────────────────────────
27    /// The core force holding the entity together. Usually Gravity.
28    pub binding_field: ForceField,
29    /// IDs of glyphs that compose this entity.
30    pub glyph_ids: Vec<GlyphId>,
31    /// Target positions (relative to entity center) for each glyph.
32    pub formation: Vec<Vec3>,
33    /// Character assigned to each formation slot.
34    pub formation_chars: Vec<char>,
35    /// Color assigned to each formation slot.
36    pub formation_colors: Vec<Vec4>,
37
38    // ── Stats that drive visuals ──────────────────────────────────────────────
39    pub hp: f32,
40    pub max_hp: f32,
41    pub entity_mass: f32,
42    pub entity_temperature: f32,
43    pub entity_entropy: f32,
44
45    // ── Visual state ─────────────────────────────────────────────────────────
46    /// 0.0 = fully dispersed, 1.0 = tight formation.
47    /// Driven by hp/max_hp: cohesion = (hp / max_hp).sqrt()
48    pub cohesion: f32,
49
50    /// The entity's pulse function (breathing/heartbeat rhythm).
51    pub pulse_rate: f32,   // Hz
52    pub pulse_depth: f32,  // amplitude of pulse oscillation
53
54    // ── Internal animation time ───────────────────────────────────────────────
55    pub age: f32,
56
57    // ── Lifecycle ─────────────────────────────────────────────────────────────
58    /// Arbitrary string tags for filtering/grouping.
59    pub tags: Vec<String>,
60    /// Set to true to remove the entity on the next GC pass.
61    pub despawn_requested: bool,
62}
63
64impl AmorphousEntity {
65    pub fn new(name: impl Into<String>, position: Vec3) -> Self {
66        Self {
67            name: name.into(),
68            position,
69            binding_field: ForceField::Gravity {
70                center: position,
71                strength: 5.0,
72                falloff: crate::math::Falloff::InverseSquare,
73            },
74            glyph_ids: Vec::new(),
75            formation: Vec::new(),
76            formation_chars: Vec::new(),
77            formation_colors: Vec::new(),
78            hp: 100.0,
79            max_hp: 100.0,
80            entity_mass: 10.0,
81            entity_temperature: 0.5,
82            entity_entropy: 0.1,
83            cohesion: 1.0,
84            pulse_rate: 1.0,
85            pulse_depth: 0.05,
86            age: 0.0,
87            tags: Vec::new(),
88            despawn_requested: false,
89        }
90    }
91
92    /// HP fraction [0, 1].
93    pub fn hp_frac(&self) -> f32 {
94        (self.hp / self.max_hp.max(0.001)).clamp(0.0, 1.0)
95    }
96
97    /// Update cohesion based on current HP.
98    pub fn update_cohesion(&mut self) {
99        // Low HP = lower cohesion (entity falls apart)
100        self.cohesion = self.hp_frac().sqrt();
101    }
102
103    /// Advance entity time. Returns true if the entity should be removed (hp <= 0).
104    pub fn tick(&mut self, dt: f32, _time: f32) -> bool {
105        self.age += dt;
106        self.update_cohesion();
107        self.hp <= 0.0
108    }
109
110    /// Apply damage to this entity.
111    pub fn take_damage(&mut self, amount: f32) {
112        self.hp = (self.hp - amount).max(0.0);
113    }
114
115    /// Return true if the entity is dead.
116    pub fn is_dead(&self) -> bool { self.hp <= 0.0 }
117}
118
119impl Default for AmorphousEntity {
120    fn default() -> Self { Self::new("", Vec3::ZERO) }
121}