Skip to main content

oxihuman_morph/
flush_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Skin flush/blush morph stub.
6
7/// Flush trigger cause.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum FlushCause {
10    Emotion,
11    Heat,
12    Exercise,
13    Alcohol,
14    Rosacea,
15}
16
17/// Skin flush morph controller.
18#[derive(Debug, Clone)]
19pub struct FlushMorph {
20    pub cause: FlushCause,
21    pub intensity: f32,
22    pub spread: f32,
23    pub morph_count: usize,
24    pub enabled: bool,
25}
26
27impl FlushMorph {
28    pub fn new(morph_count: usize) -> Self {
29        FlushMorph {
30            cause: FlushCause::Emotion,
31            intensity: 0.0,
32            spread: 0.5,
33            morph_count,
34            enabled: true,
35        }
36    }
37}
38
39/// Create a new flush morph.
40pub fn new_flush_morph(morph_count: usize) -> FlushMorph {
41    FlushMorph::new(morph_count)
42}
43
44/// Set flush cause.
45pub fn flm_set_cause(morph: &mut FlushMorph, cause: FlushCause) {
46    morph.cause = cause;
47}
48
49/// Set flush intensity.
50pub fn flm_set_intensity(morph: &mut FlushMorph, intensity: f32) {
51    morph.intensity = intensity.clamp(0.0, 1.0);
52}
53
54/// Set spread factor.
55pub fn flm_set_spread(morph: &mut FlushMorph, spread: f32) {
56    morph.spread = spread.clamp(0.0, 1.0);
57}
58
59/// Evaluate morph weights (stub: uniform from intensity).
60pub fn flm_evaluate(morph: &FlushMorph) -> Vec<f32> {
61    /* Stub: uniform weight from intensity */
62    if !morph.enabled || morph.morph_count == 0 {
63        return vec![];
64    }
65    vec![morph.intensity; morph.morph_count]
66}
67
68/// Enable or disable.
69pub fn flm_set_enabled(morph: &mut FlushMorph, enabled: bool) {
70    morph.enabled = enabled;
71}
72
73/// Serialize to JSON-like string.
74pub fn flm_to_json(morph: &FlushMorph) -> String {
75    let cause = match morph.cause {
76        FlushCause::Emotion => "emotion",
77        FlushCause::Heat => "heat",
78        FlushCause::Exercise => "exercise",
79        FlushCause::Alcohol => "alcohol",
80        FlushCause::Rosacea => "rosacea",
81    };
82    format!(
83        r#"{{"cause":"{}","intensity":{},"spread":{},"morph_count":{},"enabled":{}}}"#,
84        cause, morph.intensity, morph.spread, morph.morph_count, morph.enabled
85    )
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_default_cause() {
94        let m = new_flush_morph(4);
95        assert_eq!(
96            m.cause,
97            FlushCause::Emotion /* default cause must be Emotion */
98        );
99    }
100
101    #[test]
102    fn test_set_cause() {
103        let mut m = new_flush_morph(4);
104        flm_set_cause(&mut m, FlushCause::Exercise);
105        assert_eq!(m.cause, FlushCause::Exercise /* cause must be set */);
106    }
107
108    #[test]
109    fn test_intensity_clamp() {
110        let mut m = new_flush_morph(4);
111        flm_set_intensity(&mut m, 1.5);
112        assert!((m.intensity - 1.0).abs() < 1e-6 /* clamped to 1.0 */);
113    }
114
115    #[test]
116    fn test_spread_clamp() {
117        let mut m = new_flush_morph(4);
118        flm_set_spread(&mut m, -0.5);
119        assert!(m.spread.abs() < 1e-6 /* clamped to 0.0 */);
120    }
121
122    #[test]
123    fn test_evaluate_length() {
124        let mut m = new_flush_morph(5);
125        flm_set_intensity(&mut m, 0.4);
126        assert_eq!(
127            flm_evaluate(&m).len(),
128            5 /* output must match morph_count */
129        );
130    }
131
132    #[test]
133    fn test_evaluate_disabled() {
134        let mut m = new_flush_morph(4);
135        flm_set_enabled(&mut m, false);
136        assert!(flm_evaluate(&m).is_empty() /* disabled must return empty */);
137    }
138
139    #[test]
140    fn test_to_json_has_cause() {
141        let m = new_flush_morph(4);
142        let j = flm_to_json(&m);
143        assert!(j.contains("\"cause\"") /* JSON must have cause */);
144    }
145
146    #[test]
147    fn test_enabled_default() {
148        let m = new_flush_morph(4);
149        assert!(m.enabled /* must be enabled by default */);
150    }
151
152    #[test]
153    fn test_evaluate_matches_intensity() {
154        let mut m = new_flush_morph(3);
155        flm_set_intensity(&mut m, 0.8);
156        let out = flm_evaluate(&m);
157        assert!((out[0] - 0.8).abs() < 1e-5 /* evaluate must match intensity */);
158    }
159}