muninn-contracts: Muninn's 15 state records leave odin-core

New library crate crates/muninn-contracts holds the records Muninn owns
(telemetry surface, capture stream and command, OBS catalog, Move marker
candidate, controller state, identity, light command, hue program,
tracker health and evidence transport health, HID controller state,
Quest access, command boundary, transport profile), their schema
constants, and the MuninnRecords document set. The definitions are the
odin-core ones moved verbatim; the wire is unchanged.

muninn and sleipnir import the records from here, so both link one
definition. muninn registers MuninnRecords after OdinDocuments, which
stays because it still carries Odin's own records and stale copies of
the same schema ids. Muninn's odin-core imports are now
EveProviderAdvertisementRecord, EveSurfaceStateRecord and OdinDocuments.

Tests pin every type name and schema id, the document-set bindings, and
the positional wire layout of two records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 23:31:46 +02:00
commit e27de13efd
9 changed files with 739 additions and 11 deletions

15
Cargo.lock generated
View file

@ -479,6 +479,19 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "muninn-contracts"
version = "0.1.0"
dependencies = [
"anyhow",
"cultcache-rs",
"cultmesh-rs",
"cultnet-rs",
"rmp-serde",
"serde",
"serde_json",
]
[[package]]
name = "muninn-daemon"
version = "0.1.0"
@ -488,6 +501,7 @@ dependencies = [
"cultmesh-rs",
"cultnet-rs",
"libc",
"muninn-contracts",
"muninn-move-tracker",
"muninn-psmoveapi-tracker",
"odin-core",
@ -799,6 +813,7 @@ dependencies = [
"cultcache-rs",
"cultmesh-rs",
"cultnet-rs",
"muninn-contracts",
"odin-core",
"rmp-serde",
"serde_json",

View file

@ -1,5 +1,6 @@
[workspace]
members = [
"crates/muninn-contracts",
"crates/muninn-daemon",
"crates/muninn-move-tracker",
"crates/muninn-psmoveapi-tracker",

View file

@ -0,0 +1,21 @@
[package]
name = "muninn-contracts"
version = "0.1.0"
edition.workspace = true
license.workspace = true
publish.workspace = true
[lib]
name = "muninn_contracts"
path = "src/lib.rs"
[dependencies]
anyhow = "1"
cultcache-rs = { workspace = true }
cultmesh-rs = { workspace = true }
cultnet-rs = { workspace = true }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[dev-dependencies]
rmp-serde = "1.3"

View file

@ -0,0 +1,176 @@
//! Muninn's typed state records: the wire and store contract that `muninn` and
//! `sleipnir` share. Library only: no sockets, threads, devices or Odin.
mod records;
pub use records::*;
cultmesh_rs::cultmesh_documents!(MuninnRecords {
MuninnTelemetrySurfaceRecord => MUNINN_TELEMETRY_SURFACE_SCHEMA,
MuninnCaptureStreamRecord => MUNINN_CAPTURE_STREAM_SCHEMA,
MuninnCaptureStreamCommandRecord => MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA,
MuninnObsStreamCatalogRecord => MUNINN_OBS_STREAM_CATALOG_SCHEMA,
MuninnMoveMarkerCandidateRecord => MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA,
MuninnMoveControllerStateRecord => MUNINN_MOVE_CONTROLLER_STATE_SCHEMA,
MuninnHidControllerStateRecord => MUNINN_HID_CONTROLLER_STATE_SCHEMA,
MuninnMoveIdentityRecord => MUNINN_MOVE_IDENTITY_SCHEMA,
MuninnMoveLightCommandRecord => MUNINN_MOVE_LIGHT_COMMAND_SCHEMA,
MuninnMoveHueProgramRecord => MUNINN_MOVE_HUE_PROGRAM_SCHEMA,
MuninnMoveTrackerHealthRecord => MUNINN_MOVE_TRACKER_HEALTH_SCHEMA,
MuninnMoveEvidenceTransportHealthRecord => MUNINN_MOVE_EVIDENCE_TRANSPORT_HEALTH_SCHEMA,
MuninnQuestAccessRecord => MUNINN_QUEST_ACCESS_SCHEMA,
MuninnCommandBoundaryCompatRecord => MUNINN_COMMAND_BOUNDARY_SCHEMA,
MuninnTransportProfileCompatRecord => MUNINN_TRANSPORT_PROFILE_SCHEMA,
});
#[cfg(test)]
mod tests {
use super::*;
use cultcache_rs::{CultCache, DatabaseEntry};
use cultmesh_rs::CultMeshDocumentSet;
use cultnet_rs::CultNetDocumentRegistry;
/// Every Muninn record: its type name, its schema id, the schema constant
/// the document set binds it under, and its Rust name.
fn contract() -> Vec<(&'static str, &'static str, &'static str, &'static str)> {
macro_rules! row {
($record:ty, $constant:expr) => {
(
<$record as DatabaseEntry>::TYPE,
<$record as DatabaseEntry>::SCHEMA_NAME,
$constant,
stringify!($record),
)
};
}
vec![
row!(MuninnTelemetrySurfaceRecord, MUNINN_TELEMETRY_SURFACE_SCHEMA),
row!(MuninnCaptureStreamRecord, MUNINN_CAPTURE_STREAM_SCHEMA),
row!(MuninnCaptureStreamCommandRecord, MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA),
row!(MuninnObsStreamCatalogRecord, MUNINN_OBS_STREAM_CATALOG_SCHEMA),
row!(MuninnMoveMarkerCandidateRecord, MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA),
row!(MuninnMoveControllerStateRecord, MUNINN_MOVE_CONTROLLER_STATE_SCHEMA),
row!(MuninnHidControllerStateRecord, MUNINN_HID_CONTROLLER_STATE_SCHEMA),
row!(MuninnMoveIdentityRecord, MUNINN_MOVE_IDENTITY_SCHEMA),
row!(MuninnMoveLightCommandRecord, MUNINN_MOVE_LIGHT_COMMAND_SCHEMA),
row!(MuninnMoveHueProgramRecord, MUNINN_MOVE_HUE_PROGRAM_SCHEMA),
row!(MuninnMoveTrackerHealthRecord, MUNINN_MOVE_TRACKER_HEALTH_SCHEMA),
row!(
MuninnMoveEvidenceTransportHealthRecord,
MUNINN_MOVE_EVIDENCE_TRANSPORT_HEALTH_SCHEMA
),
row!(MuninnQuestAccessRecord, MUNINN_QUEST_ACCESS_SCHEMA),
row!(MuninnCommandBoundaryCompatRecord, MUNINN_COMMAND_BOUNDARY_SCHEMA),
row!(MuninnTransportProfileCompatRecord, MUNINN_TRANSPORT_PROFILE_SCHEMA),
]
}
#[test]
fn record_names_are_the_published_schema_ids() {
let expected = [
("muninn.telemetry_surface", "muninn.telemetry_surface.v1"),
("muninn.capture_stream", "muninn.capture_stream.v1"),
("muninn.capture_stream_command", "muninn.capture_stream_command.v1"),
("muninn.obs_stream_catalog", "muninn.obs_stream_catalog.v1"),
("muninn.move_marker_candidate", "muninn.move_marker_candidate.v1"),
("muninn.move_controller_state", "muninn.move_controller_state.v1"),
("muninn.hid_controller_state", "muninn.hid_controller_state.v1"),
("muninn.move_identity", "muninn.move_identity.v1"),
("muninn.move_light_command", "muninn.move_light_command.v1"),
("muninn.move_hue_program", "muninn.move_hue_program.v1"),
("muninn.move_tracker_health", "muninn.move_tracker_health.v1"),
(
"muninn.move_evidence_transport_health",
"muninn.move_evidence_transport_health.v1",
),
("muninn.quest_access", "muninn.quest_access.v1"),
("muninn.command_boundary", "muninn.command_boundary.v1"),
("muninn.transport_profile", "muninn.transport_profile.v1"),
];
let actual: Vec<_> = contract()
.into_iter()
.map(|(record_type, schema, constant, _)| {
assert_eq!(schema, constant, "schema constant and derive disagree");
(record_type, schema)
})
.collect();
assert_eq!(actual, expected);
}
#[test]
fn document_set_binds_every_record_under_its_schema() {
let mut cache = CultCache::new();
MuninnRecords.register_cache(&mut cache).unwrap();
let mut registry = CultNetDocumentRegistry::new();
MuninnRecords.register_documents(&mut registry).unwrap();
let rows = contract();
let mut registered = cache.registered_entry_types();
registered.sort();
let mut expected: Vec<String> = rows.iter().map(|row| row.0.to_string()).collect();
expected.sort();
assert_eq!(registered, expected);
for (record_type, schema, _, record) in rows {
let binding = registry
.binding_by_schema_id(schema)
.unwrap_or_else(|| panic!("{record} is not bound under {schema}"));
assert_eq!(binding.document_type, record_type, "{record}");
}
}
#[test]
fn hid_controller_state_wire_layout_is_positional_by_key() {
let record = MuninnHidControllerStateRecord {
stream_id: "muninn.raven.hid".into(),
host_id: "raven".into(),
device_id: "xbox-0".into(),
device_kind: "xinput".into(),
sequence: 7,
source_timestamp_ns: -3,
axes: vec![0.5, -1.0],
buttons: vec!["a".into(), "b".into()],
battery01: 1.0,
observed_at: "unix:100".into(),
source_path: "xinput://0".into(),
};
let bytes = rmp_serde::to_vec(&record).unwrap();
assert_eq!(
hex(&bytes),
"9bb06d756e696e6e2e726176656e2e686964a5726176656ea678626f782d30a678696e70757407fd92ca3f000000cabf80000092a161a162ca3f800000a8756e69783a313030aa78696e7075743a2f2f30"
);
assert_eq!(
rmp_serde::from_slice::<MuninnHidControllerStateRecord>(&bytes).unwrap(),
record
);
}
#[test]
fn hue_program_wire_layout_carries_its_defaulted_tail() {
let record = MuninnMoveHueProgramRecord {
program_id: "prog".into(),
host_id: "raven".into(),
mode: "cycle".into(),
cycle_ms: 20,
epoch_ns: -1,
hold_at_ns: 0,
requested_by: "mimir".into(),
updated_at: "unix:1".into(),
order_mode: "ring".into(),
transition_percent: 50,
transition_percent_explicit: true,
};
let bytes = rmp_serde::to_vec(&record).unwrap();
assert_eq!(
hex(&bytes),
"9ba470726f67a5726176656ea56379636c6514ff00a56d696d6972a6756e69783a31a472696e6732c3"
);
assert_eq!(
rmp_serde::from_slice::<MuninnMoveHueProgramRecord>(&bytes).unwrap(),
record
);
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
}

View file

@ -0,0 +1,508 @@
use cultcache_rs::DatabaseEntry;
use serde_json::Value;
pub const MUNINN_TELEMETRY_SURFACE_SCHEMA: &str = "muninn.telemetry_surface.v1";
pub const MUNINN_CAPTURE_STREAM_SCHEMA: &str = "muninn.capture_stream.v1";
pub const MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA: &str = "muninn.capture_stream_command.v1";
pub const MUNINN_OBS_STREAM_CATALOG_SCHEMA: &str = "muninn.obs_stream_catalog.v1";
pub const MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA: &str = "muninn.move_marker_candidate.v1";
pub const MUNINN_MOVE_CONTROLLER_STATE_SCHEMA: &str = "muninn.move_controller_state.v1";
pub const MUNINN_HID_CONTROLLER_STATE_SCHEMA: &str = "muninn.hid_controller_state.v1";
pub const MUNINN_MOVE_IDENTITY_SCHEMA: &str = "muninn.move_identity.v1";
pub const MUNINN_MOVE_LIGHT_COMMAND_SCHEMA: &str = "muninn.move_light_command.v1";
pub const MUNINN_MOVE_HUE_PROGRAM_SCHEMA: &str = "muninn.move_hue_program.v1";
pub const MUNINN_MOVE_TRACKER_HEALTH_SCHEMA: &str = "muninn.move_tracker_health.v1";
pub const MUNINN_MOVE_EVIDENCE_TRANSPORT_HEALTH_SCHEMA: &str =
"muninn.move_evidence_transport_health.v1";
pub const MUNINN_QUEST_ACCESS_SCHEMA: &str = "muninn.quest_access.v1";
pub const MUNINN_COMMAND_BOUNDARY_SCHEMA: &str = "muninn.command_boundary.v1";
pub const MUNINN_TRANSPORT_PROFILE_SCHEMA: &str = "muninn.transport_profile.v1";
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "muninn.capture_stream", schema = "muninn.capture_stream.v1")]
pub struct MuninnCaptureStreamRecord {
#[cultcache(key = 0)]
pub stream_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub state: String,
#[cultcache(key = 3)]
pub video_source: String,
#[cultcache(key = 4)]
pub audio_source: String,
#[cultcache(key = 5)]
pub transport: String,
#[cultcache(key = 6)]
pub targets: Vec<String>,
#[cultcache(key = 7)]
pub command_witness: String,
#[cultcache(key = 8)]
pub supervisor_pid: Option<u32>,
#[cultcache(key = 9)]
pub mux_pid: Option<u32>,
#[cultcache(key = 10)]
pub restart_count: u32,
#[cultcache(key = 11)]
pub detail: String,
#[cultcache(key = 12)]
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "muninn.capture_stream_command",
schema = "muninn.capture_stream_command.v1"
)]
pub struct MuninnCaptureStreamCommandRecord {
#[cultcache(key = 0)]
pub command_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub stream_id: String,
#[cultcache(key = 3)]
pub state: String,
#[cultcache(key = 4)]
pub action: String,
#[cultcache(key = 5)]
pub target_host: String,
#[cultcache(key = 6)]
pub port: u16,
#[cultcache(key = 7)]
pub obs_target_host: Option<String>,
#[cultcache(key = 8)]
pub obs_port: u16,
#[cultcache(key = 9)]
pub media_transport: String,
#[cultcache(key = 10)]
pub media_packet_bytes: u32,
#[cultcache(key = 11)]
pub requested_by: String,
#[cultcache(key = 12)]
pub detail: String,
#[cultcache(key = 13)]
pub updated_at: String,
#[cultcache(key = 14, default)]
pub rudp_video_bitrate_kbps: u32,
#[cultcache(key = 15, default)]
pub rudp_latency_budget_ms: u32,
#[cultcache(key = 16, default)]
pub video_source_id: String,
#[cultcache(key = 17, default)]
pub audio_source_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "muninn.telemetry_surface",
schema = "muninn.telemetry_surface.v1"
)]
pub struct MuninnTelemetrySurfaceRecord {
#[cultcache(key = 0)]
pub surface_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub state: String,
#[cultcache(key = 3)]
pub available_sources: Vec<String>,
#[cultcache(key = 4)]
pub stream_affordances: Vec<String>,
#[cultcache(key = 5)]
pub active_streams: Vec<String>,
#[cultcache(key = 6)]
pub activation_authority: String,
#[cultcache(key = 7)]
pub detail: String,
#[cultcache(key = 8)]
pub updated_at: String,
#[cultcache(key = 9, default)]
pub primary_stream_id: String,
#[cultcache(key = 10, default)]
pub primary_stream_label: String,
#[cultcache(key = 11, default)]
pub command_rudp_target: String,
#[cultcache(key = 12, default)]
pub media_target_host: String,
#[cultcache(key = 13, default)]
pub media_port: u16,
#[cultcache(key = 14, default)]
pub media_packet_bytes: u32,
#[cultcache(key = 15, default)]
pub rudp_video_bitrate_kbps: u32,
#[cultcache(key = 16, default)]
pub rudp_latency_budget_ms: u32,
#[cultcache(key = 17, default)]
pub video_source_ids: Vec<String>,
#[cultcache(key = 18, default)]
pub video_source_labels: Vec<String>,
#[cultcache(key = 19, default)]
pub audio_source_ids: Vec<String>,
#[cultcache(key = 20, default)]
pub audio_source_labels: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "muninn.obs_stream_catalog",
schema = "muninn.obs_stream_catalog.v1"
)]
pub struct MuninnObsStreamCatalogRecord {
#[cultcache(key = 0)]
pub catalog_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub stream_ids: Vec<String>,
#[cultcache(key = 3)]
pub labels: Vec<String>,
#[cultcache(key = 4)]
pub urls: Vec<String>,
#[cultcache(key = 5)]
pub states: Vec<String>,
#[cultcache(key = 6)]
pub updated_at: String,
#[cultcache(key = 7, default)]
pub command_rudp_target: String,
#[cultcache(key = 8, default)]
pub media_target_host: String,
#[cultcache(key = 9, default)]
pub media_port: u16,
#[cultcache(key = 10, default)]
pub media_packet_bytes: u32,
#[cultcache(key = 11, default)]
pub rudp_video_bitrate_kbps: u32,
#[cultcache(key = 12, default)]
pub rudp_latency_budget_ms: u32,
#[cultcache(key = 13, default)]
pub video_source_ids: Vec<String>,
#[cultcache(key = 14, default)]
pub video_source_labels: Vec<String>,
#[cultcache(key = 15, default)]
pub audio_source_ids: Vec<String>,
#[cultcache(key = 16, default)]
pub audio_source_labels: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "muninn.move_marker_candidate",
schema = "muninn.move_marker_candidate.v1"
)]
pub struct MuninnMoveMarkerCandidateRecord {
#[cultcache(key = 0)]
pub stream_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub camera_id: String,
#[cultcache(key = 3)]
pub frame_sequence: u64,
#[cultcache(key = 4)]
pub source_id_hash: u64,
#[cultcache(key = 5)]
pub tile_x: u32,
#[cultcache(key = 6)]
pub tile_y: u32,
#[cultcache(key = 7)]
pub center_x_px: f32,
#[cultcache(key = 8)]
pub center_y_px: f32,
#[cultcache(key = 9)]
pub radius_px: f32,
#[cultcache(key = 10)]
pub area_px: u32,
#[cultcache(key = 11)]
pub mean_luma: f32,
#[cultcache(key = 12)]
pub peak_luma: u32,
#[cultcache(key = 13)]
pub score: f32,
#[cultcache(key = 14)]
pub observed_at: String,
#[cultcache(key = 15, default)]
pub move_id: String,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "muninn.move_controller_state",
schema = "muninn.move_controller_state.v1"
)]
pub struct MuninnMoveControllerStateRecord {
#[cultcache(key = 0)]
pub stream_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub move_id: String,
#[cultcache(key = 3)]
pub sequence: u64,
#[cultcache(key = 4)]
pub source_timestamp_ns: i64,
#[cultcache(key = 5)]
pub accelerometer_xyz: Vec<f32>,
#[cultcache(key = 6)]
pub gyroscope_xyz: Vec<f32>,
#[cultcache(key = 7)]
pub magnetometer_xyz: Vec<f32>,
#[cultcache(key = 8)]
pub trigger_value: f32,
#[cultcache(key = 9)]
pub buttons: Vec<String>,
#[cultcache(key = 10)]
pub battery01: f32,
#[cultcache(key = 11)]
pub observed_at: String,
#[cultcache(key = 12, default)]
pub source_path: String,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "muninn.hid_controller_state",
schema = "muninn.hid_controller_state.v1"
)]
pub struct MuninnHidControllerStateRecord {
#[cultcache(key = 0)]
pub stream_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub device_id: String,
#[cultcache(key = 3)]
pub device_kind: String,
#[cultcache(key = 4)]
pub sequence: u64,
#[cultcache(key = 5)]
pub source_timestamp_ns: i64,
#[cultcache(key = 6)]
pub axes: Vec<f32>,
#[cultcache(key = 7)]
pub buttons: Vec<String>,
#[cultcache(key = 8)]
pub battery01: f32,
#[cultcache(key = 9)]
pub observed_at: String,
#[cultcache(key = 10)]
pub source_path: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "muninn.move_identity", schema = "muninn.move_identity.v1")]
pub struct MuninnMoveIdentityRecord {
#[cultcache(key = 0)]
pub identity_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub move_id: String,
#[cultcache(key = 3)]
pub source_path: String,
#[cultcache(key = 4)]
pub bluetooth_host_address: String,
#[cultcache(key = 5)]
pub state: String,
#[cultcache(key = 6)]
pub detail: String,
#[cultcache(key = 7)]
pub observed_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "muninn.move_light_command",
schema = "muninn.move_light_command.v1"
)]
pub struct MuninnMoveLightCommandRecord {
#[cultcache(key = 0)]
pub command_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub move_id: String,
#[cultcache(key = 3)]
pub hidraw_path: String,
#[cultcache(key = 4)]
pub colors: Vec<String>,
#[cultcache(key = 5)]
pub durations_ms: Vec<u32>,
#[cultcache(key = 6)]
pub repeat_count: u32,
#[cultcache(key = 7)]
pub authority: String,
#[cultcache(key = 8)]
pub state: String,
#[cultcache(key = 9)]
pub detail: String,
#[cultcache(key = 10)]
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "muninn.move_hue_program",
schema = "muninn.move_hue_program.v1"
)]
pub struct MuninnMoveHueProgramRecord {
#[cultcache(key = 0)]
pub program_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub mode: String,
#[cultcache(key = 3)]
pub cycle_ms: u64,
#[cultcache(key = 4)]
pub epoch_ns: i64,
#[cultcache(key = 5)]
pub hold_at_ns: i64,
#[cultcache(key = 6)]
pub requested_by: String,
#[cultcache(key = 7)]
pub updated_at: String,
#[cultcache(key = 8, default)]
pub order_mode: String,
#[cultcache(key = 9, default)]
pub transition_percent: u8,
#[cultcache(key = 10, default)]
pub transition_percent_explicit: bool,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "muninn.move_tracker_health",
schema = "muninn.move_tracker_health.v1"
)]
pub struct MuninnMoveTrackerHealthRecord {
#[cultcache(key = 0)]
pub health_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub camera_id: String,
#[cultcache(key = 3)]
pub camera_index: i32,
#[cultcache(key = 4)]
pub state: String,
#[cultcache(key = 5)]
pub camera_name: String,
#[cultcache(key = 6)]
pub camera_api: String,
#[cultcache(key = 7)]
pub width: u32,
#[cultcache(key = 8)]
pub height: u32,
#[cultcache(key = 9)]
pub exposure: f32,
#[cultcache(key = 10)]
pub calibrated_controller_count: u32,
#[cultcache(key = 11)]
pub update_count: u64,
#[cultcache(key = 12)]
pub observation_count: u64,
#[cultcache(key = 13)]
pub latest_observation_count: u32,
#[cultcache(key = 14)]
pub last_observation_at: String,
#[cultcache(key = 15)]
pub detail: String,
#[cultcache(key = 16)]
pub updated_at: String,
#[cultcache(key = 17, default)]
pub image_mean_rgb: Vec<u32>,
#[cultcache(key = 18, default)]
pub image_peak_rgb: Vec<u32>,
#[cultcache(key = 19, default)]
pub color_evidence_move_ids: Vec<String>,
#[cultcache(key = 20, default)]
pub color_evidence_pixel_counts: Vec<u32>,
#[cultcache(key = 21, default)]
pub rejected_stale_count: u64,
#[cultcache(key = 22, default)]
pub rejected_radius_count: u64,
#[cultcache(key = 23, default)]
pub rejected_bounds_count: u64,
#[cultcache(key = 24, default)]
pub rejected_continuity_count: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "muninn.move_evidence_transport_health",
schema = "muninn.move_evidence_transport_health.v1"
)]
pub struct MuninnMoveEvidenceTransportHealthRecord {
#[cultcache(key = 0)]
pub health_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub stream_id: String,
#[cultcache(key = 3)]
pub produced_frames: u64,
#[cultcache(key = 4)]
pub local_ring_admissions: u64,
#[cultcache(key = 5)]
pub remote_handoffs: u64,
#[cultcache(key = 6)]
pub remote_sends: u64,
#[cultcache(key = 7)]
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "muninn.quest_access", schema = "muninn.quest_access.v1")]
pub struct MuninnQuestAccessRecord {
#[cultcache(key = 0)]
pub access_id: String,
#[cultcache(key = 1)]
pub host_id: String,
#[cultcache(key = 2)]
pub serial: String,
#[cultcache(key = 3)]
pub connection_state: String,
#[cultcache(key = 4)]
pub product: String,
#[cultcache(key = 5)]
pub model: String,
#[cultcache(key = 6)]
pub device: String,
#[cultcache(key = 7)]
pub transport_id: String,
#[cultcache(key = 8)]
pub input_stream_id: String,
#[cultcache(key = 9)]
pub pose_stream_id: String,
#[cultcache(key = 10)]
pub video_input_stream_id: String,
#[cultcache(key = 11)]
pub video_input_transport: String,
#[cultcache(key = 12)]
pub state: String,
#[cultcache(key = 13)]
pub detail: String,
#[cultcache(key = 14)]
pub observed_at: String,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "muninn.command_boundary",
schema = "muninn.command_boundary.v1"
)]
pub struct MuninnCommandBoundaryCompatRecord {
#[cultcache(key = 0)]
pub value: Value,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "muninn.transport_profile",
schema = "muninn.transport_profile.v1"
)]
pub struct MuninnTransportProfileCompatRecord {
#[cultcache(key = 0)]
pub value: Value,
}

View file

@ -19,6 +19,7 @@ cultcache-rs = { workspace = true }
cultmesh-rs = { workspace = true }
cultnet-rs = { workspace = true }
libc = "0.2"
muninn-contracts = { path = "../muninn-contracts" }
muninn-move-tracker = { path = "../muninn-move-tracker" }
muninn-psmoveapi-tracker = { path = "../muninn-psmoveapi-tracker", optional = true }
odin-core = { workspace = true }

View file

@ -23,18 +23,18 @@ use cultnet_rs::{
GameCultRuntimePresenceHealthRecord, decode_cultnet_message_from_slice,
media_stream_request_key, validate_media_stream_request,
};
#[cfg(feature = "psmoveapi-tracker")]
use odin_core::MuninnMoveTrackerHealthRecord;
use odin_core::{
EveProviderAdvertisementRecord, EveSurfaceStateRecord,
use muninn_contracts::{
MUNINN_MOVE_HUE_PROGRAM_SCHEMA, MUNINN_OBS_STREAM_CATALOG_SCHEMA,
MuninnCaptureStreamCommandRecord, MuninnCaptureStreamRecord, MuninnCommandBoundaryCompatRecord,
MuninnHidControllerStateRecord, MuninnMoveControllerStateRecord,
MuninnMoveEvidenceTransportHealthRecord, MuninnMoveHueProgramRecord, MuninnMoveIdentityRecord,
MuninnMoveLightCommandRecord, MuninnMoveMarkerCandidateRecord, MuninnObsStreamCatalogRecord,
MuninnQuestAccessRecord, MuninnTelemetrySurfaceRecord, MuninnTransportProfileCompatRecord,
OdinDocuments,
MuninnQuestAccessRecord, MuninnRecords, MuninnTelemetrySurfaceRecord,
MuninnTransportProfileCompatRecord,
};
#[cfg(feature = "psmoveapi-tracker")]
use muninn_contracts::MuninnMoveTrackerHealthRecord;
use odin_core::{EveProviderAdvertisementRecord, EveSurfaceStateRecord, OdinDocuments};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::{HashMap, HashSet};
@ -9526,15 +9526,18 @@ fn ensure_state_dirs(options: &Options) -> Result<()> {
Ok(())
}
/// Odin's document set plus the producer-agnostic media stream contract.
/// Muninn advertises through the latter and takes requests through it; the
/// former is what the rest of its state still speaks.
/// Odin's document set, Muninn's own records, and the producer-agnostic media
/// stream contract. Muninn's records are registered after Odin's, so
/// `muninn-contracts` decides every `muninn.*` binding; Odin's document set
/// still carries stale copies of the same schema ids (and the store may hold
/// legacy Odin records), which is why it stays registered.
#[derive(Clone, Copy, Debug, Default)]
struct MuninnDocuments;
impl cultmesh_rs::CultMeshDocumentSet for MuninnDocuments {
fn register_cache(&self, cache: &mut cultcache_rs::CultCache) -> Result<()> {
OdinDocuments.register_cache(cache)?;
MuninnRecords.register_cache(cache)?;
cache.register_entry_type::<GameCultMediaStreamAdvertisementRecord>()?;
cache.register_entry_type::<GameCultMediaStreamRequestRecord>()?;
cache.register_entry_type::<GameCultRuntimePresenceHealthRecord>()?;
@ -9543,6 +9546,7 @@ impl cultmesh_rs::CultMeshDocumentSet for MuninnDocuments {
fn register_documents(&self, registry: &mut cultnet_rs::CultNetDocumentRegistry) -> Result<()> {
OdinDocuments.register_documents(registry)?;
MuninnRecords.register_documents(registry)?;
registry.register(
cultnet_rs::CultNetDocumentBinding::for_entry_with_schema_id::<
GameCultMediaStreamAdvertisementRecord,

View file

@ -14,6 +14,7 @@ anyhow = "1"
cultmesh-rs = { workspace = true }
cultnet-rs = { workspace = true }
cultcache-rs = { workspace = true }
muninn-contracts = { path = "../muninn-contracts" }
odin-core = { workspace = true }
rmp-serde = "1.3"
serde_json = "1"

View file

@ -7,10 +7,11 @@ use cultnet_rs::{
CultNetRudpSocketTransportConnection, CultNetRudpSocketTransportOptions, CultNetWireContract,
encode_cultnet_message_to_vec,
};
use muninn_contracts::{MUNINN_HID_CONTROLLER_STATE_SCHEMA, MuninnHidControllerStateRecord};
use odin_core::{
EVE_PROVIDER_ADVERTISEMENT_SCHEMA, EveProviderAdvertisementRecord, EveSurfaceStateRecord,
IdunnDaemonHealthRecord, MUNINN_HID_CONTROLLER_STATE_SCHEMA, MuninnHidControllerStateRecord,
OdinDocuments, OdinEndpointQuery, SleipnirInputMappingRecord, discover_provider_endpoints,
IdunnDaemonHealthRecord, OdinDocuments, OdinEndpointQuery, SleipnirInputMappingRecord,
discover_provider_endpoints,
};
use std::collections::HashMap;
use std::env;