Watch
1
0
Fork
You've already forked souveraine
0

nervous: the plexus declares the phone's body and speaks it

phone_plexus leaves the test module and takes the clock. It grows
`attended`, derived by sessiond rather than reported by a sensor, and
the two grip gauges that nothing reports yet — the gap on the record
instead of a false calm. to_json renders every field's belief, trend,
locus, health and pressure, the ranked pressures, homeostatic cost and
the unreachable list for the IPC surface; unknown stays null, never
zero.
This commit is contained in:
Fimeg 2026-08-15 16:56:25 -04:00
commit f3fcdd0cc4

View file

@ -17,10 +17,12 @@
//!
//! Fields are named rather than struct'd, so a new afferent arrives as a
//! builder call. The phone currently offers: proximity, motion, light,
//! touch (iio); charge and thermal (power); and the USB-C port — attached,
//! data role, peer display, peer input, peer network — which usb-signaller
//! and smoo make legible. Grip, per gauge rather than squeezed to a bool,
//! is owed.
//! touch (iio); charge and thermal (power); `attended`, which sessiond's
//! clock derives from those; and the USB-C port — attached, data role, peer
//! display, peer input, peer network — which usb-signaller and smoo make
//! legible. Grip, per gauge rather than squeezed to a bool, is declared as
//! `grip_left` / `grip_right` and reads unknown until the device-side
//! reporter exists — the gap made visible, not a false calm.
use std::collections::HashMap;
use std::time::Duration;
@ -512,6 +514,87 @@ impl SomaticPlexus {
.sum()
}
/// The body as it speaks on the IPC surface: every field's belief, trend,
/// locus and source health, the bounded fields' pressures ranked worst
/// first, and the unreachable list. Unknown stays null — never zero.
pub fn to_json(&self, now: DateTime<Utc>) -> serde_json::Value {
let fields: serde_json::Map<String, serde_json::Value> = self
.fields
.iter()
.map(|e| {
let belief = e.belief(now);
let locus = match &e.locus {
Locus::Inner { regulator } => serde_json::json!({
"side": "inner",
"regulator": regulator,
}),
Locus::Boundary => serde_json::json!({ "side": "boundary" }),
};
let name = e.name.clone();
let value = serde_json::json!({
"value": belief.value,
"confidence": belief.confidence_at(now),
"trend": trend_str(belief.trend),
"locus": locus,
"health": self.source_health(&name),
"pressure": self.allostatic_pressure(&name, now),
});
(name, value)
})
.collect();
let pressures: Vec<serde_json::Value> = self
.pressures(now)
.into_iter()
.map(|(name, p)| serde_json::json!([name, p]))
.collect();
serde_json::json!({
"fields": fields,
"pressures": pressures,
"homeostatic_cost": self.homeostatic_cost(now),
"unreachable": self.unreachable(),
})
}
/// The phone's afferents as they stand — sessiond's field set, built
/// here so the tests and the daemon read one list.
///
/// iio at the boundary; charge and thermal inside it with their verbs;
/// `attended` derived by sessiond's clock rather than reported by a
/// sensor; and the two grip gauges, which nothing reports yet.
pub fn phone_plexus(now: DateTime<Utc>) -> SomaticPlexus {
SomaticPlexus::new(now)
.with_boundary_field("proximity", Duration::from_secs(30), now)
.with_boundary_field("motion", Duration::from_secs(60), now)
.with_boundary_field("light", Duration::from_secs(120), now)
.with_boundary_field("touch", Duration::from_secs(10), now)
.with_boundary_field("attended", Duration::from_secs(20), now)
.with_inner_level(
"charge",
Duration::from_secs(300),
Some("doze"),
Some(Viability::floor(0.2)),
)
.with_inner_level(
"thermal",
Duration::from_secs(120),
Some("doze"),
Some(Viability::ceiling(0.8)),
)
.with_inner_level(
"port_mode",
Duration::from_secs(600),
Some("set_usb_mode"),
None,
)
.with_boundary_level("grip_left", Duration::from_secs(30))
.with_boundary_level("grip_right", Duration::from_secs(30))
.with_expected("proximity")
.with_expected("light")
.with_expected("charge")
.with_silent_after(Duration::from_secs(90))
.with_absent_after(Duration::from_secs(300))
}
fn evaluate_health(&mut self, now: DateTime<Utc>) {
let newly_silent: Vec<(String, u64)> = self
.last_seen
@ -648,6 +731,16 @@ impl SomaticPlexus {
}
}
fn trend_str(t: Trend) -> &'static str {
match t {
Trend::FallingFast => "falling_fast",
Trend::Falling => "falling",
Trend::Stable => "stable",
Trend::Rising => "rising",
Trend::RisingFast => "rising_fast",
}
}
impl NervousNode for SomaticPlexus {
type Input = SensorReading;
type Output = f32;
@ -707,38 +800,9 @@ mod tests {
t0() + chrono::Duration::seconds(secs)
}
/// The phone's afferents as they stand: iio at the boundary, power and
/// port inside it.
/// The production field set, on the test clock.
fn phone_plexus() -> SomaticPlexus {
let now = t0();
SomaticPlexus::new(now)
.with_boundary_field("proximity", Duration::from_secs(30), now)
.with_boundary_field("motion", Duration::from_secs(60), now)
.with_boundary_field("light", Duration::from_secs(120), now)
.with_boundary_field("touch", Duration::from_secs(10), now)
.with_inner_level(
"charge",
Duration::from_secs(300),
Some("doze"),
Some(Viability::floor(0.2)),
)
.with_inner_level(
"thermal",
Duration::from_secs(120),
Some("doze"),
Some(Viability::ceiling(0.8)),
)
.with_inner_level(
"port_mode",
Duration::from_secs(600),
Some("set_usb_mode"),
None,
)
.with_expected("proximity")
.with_expected("light")
.with_expected("charge")
.with_silent_after(Duration::from_secs(90))
.with_absent_after(Duration::from_secs(300))
super::phone_plexus(t0())
}
fn read(source: &str, amount: f32) -> SensorReading {
@ -1131,7 +1195,7 @@ mod tests {
p.ingest(read("motion", 0.3), t0());
let beliefs = p.beliefs(after(1));
assert_eq!(beliefs.len(), 7);
assert_eq!(beliefs.len(), 10);
assert!(beliefs[0].is_known());
assert!(beliefs[0].value.unwrap() > 0.3);
assert!(beliefs[1].is_known());
@ -1151,6 +1215,28 @@ mod tests {
let mut p = phone_plexus();
p.ingest(read("usb_peer", 1.0), t0());
assert_eq!(p.source_health("usb_peer"), SourceHealth::Live);
assert_eq!(p.fields.len(), 7, "no field is conjured for it");
assert_eq!(p.fields.len(), 10, "no field is conjured for it");
}
#[test]
fn to_json_carries_unknown_as_null_not_zero() {
let p = phone_plexus();
let body = p.to_json(after(1));
let light = &body["fields"]["light"];
assert_eq!(
light["value"],
serde_json::Value::Null,
"never spoke must read as unknown, not calm"
);
assert_eq!(
body["fields"]["attended"]["trend"].as_str(),
Some("stable")
);
assert_eq!(
body["fields"]["charge"]["locus"]["side"].as_str(),
Some("inner")
);
assert!(body["pressures"].as_array().unwrap().is_empty());
assert!(body["unreachable"].as_array().unwrap().is_empty());
}
}