Skip to main content

oxihuman_morph/
ectomorph_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Ectomorph body type morph — lean, narrow build.
6
7/// Configuration for the ectomorph morph.
8#[derive(Debug, Clone)]
9pub struct EctomorphConfig {
10    pub limb_slenderness: f32,
11    pub shoulder_narrowness: f32,
12    pub hip_narrowness: f32,
13}
14
15impl Default for EctomorphConfig {
16    fn default() -> Self {
17        EctomorphConfig {
18            limb_slenderness: 0.75,
19            shoulder_narrowness: 0.65,
20            hip_narrowness: 0.6,
21        }
22    }
23}
24
25/// State for the ectomorph morph.
26#[derive(Debug, Clone)]
27pub struct EctomorphMorph {
28    pub intensity: f32,
29    pub config: EctomorphConfig,
30    pub enabled: bool,
31}
32
33/// Create a new ectomorph morph.
34pub fn new_ectomorph_morph() -> EctomorphMorph {
35    EctomorphMorph {
36        intensity: 0.0,
37        config: EctomorphConfig::default(),
38        enabled: true,
39    }
40}
41
42/// Set intensity [0, 1].
43pub fn ect_set_intensity(m: &mut EctomorphMorph, v: f32) {
44    m.intensity = v.clamp(0.0, 1.0);
45}
46
47/// Limb slenderness weight.
48pub fn ect_limb_weight(m: &EctomorphMorph) -> f32 {
49    m.intensity * m.config.limb_slenderness
50}
51
52/// Shoulder narrowness weight.
53pub fn ect_shoulder_weight(m: &EctomorphMorph) -> f32 {
54    m.intensity * m.config.shoulder_narrowness
55}
56
57/// Hip narrowness weight.
58pub fn ect_hip_weight(m: &EctomorphMorph) -> f32 {
59    m.intensity * m.config.hip_narrowness
60}
61
62/// Serialise to JSON.
63pub fn ect_to_json(m: &EctomorphMorph) -> String {
64    format!(
65        r#"{{"intensity":{:.3},"limb_weight":{:.3},"enabled":{}}}"#,
66        m.intensity,
67        ect_limb_weight(m),
68        m.enabled
69    )
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn default_zero_intensity() {
78        let m = new_ectomorph_morph();
79        assert!((m.intensity - 0.0).abs() < 1e-6 /* zero */);
80    }
81
82    #[test]
83    fn clamp_works() {
84        let mut m = new_ectomorph_morph();
85        ect_set_intensity(&mut m, 2.0);
86        assert!((m.intensity - 1.0).abs() < 1e-6 /* clamped */);
87    }
88
89    #[test]
90    fn limb_weight_proportional() {
91        let mut m = new_ectomorph_morph();
92        ect_set_intensity(&mut m, 1.0);
93        assert!((ect_limb_weight(&m) - m.config.limb_slenderness).abs() < 1e-6 /* proportional */);
94    }
95
96    #[test]
97    fn shoulder_narrower_than_hip_at_max() {
98        let mut m = new_ectomorph_morph();
99        ect_set_intensity(&mut m, 1.0);
100        /* ectomorphs have narrow shoulders too */
101        assert!(ect_shoulder_weight(&m) > 0.0);
102    }
103
104    #[test]
105    fn hip_weight_zero_at_zero_intensity() {
106        let m = new_ectomorph_morph();
107        assert!((ect_hip_weight(&m) - 0.0).abs() < 1e-6 /* zero hip at zero */);
108    }
109
110    #[test]
111    fn json_has_intensity() {
112        let mut m = new_ectomorph_morph();
113        ect_set_intensity(&mut m, 0.5);
114        assert!(ect_to_json(&m).contains("0.500") /* json ok */);
115    }
116
117    #[test]
118    fn enabled_flag() {
119        let m = new_ectomorph_morph();
120        assert!(m.enabled /* default enabled */);
121    }
122
123    #[test]
124    fn config_slenderness_positive() {
125        let m = new_ectomorph_morph();
126        assert!(m.config.limb_slenderness > 0.0 /* positive */);
127    }
128}