use std::sync::Arc;
use sim_citizen::CitizenRuntime;
use sim_kernel::{CapabilityName, Cx, Expr, ObjectCompat, Symbol};
use sim_lib_intent::{Origin, intent};
use sim_lib_interference_core::{SamplingCertificate, SamplingThresholds};
use sim_lib_interference_runtime::{
SamplingCertificateDescriptor, StudyDescriptor, install_interference_records,
};
use sim_lib_interference_solve::Observable;
use sim_lib_view::{DispatchContext, DispatchReason, LensRegistry, SurfaceCodec, surface};
use sim_value::{access, build};
use crate::scene::{ProjectionOptions, study_scene_with};
use crate::{
INTERFERENCE_PROJECT_CAPABILITY, INTERFERENCE_SOLVE_CAPABILITY, INTERFERENCE_SURFACE_CODEC_ID,
InterferenceSurfaceCodec, MAX_ANIMATION_FRAMES, register_interference_surface,
surface_interference_codec_symbol,
};
fn test_cx() -> Cx {
sim_kernel::testing::eager_cx()
}
fn study_expr(cx: &mut Cx) -> Expr {
let study = StudyDescriptor::example();
study.as_expr(cx).expect("Study Expr")
}
fn edit(base: &Expr, path: &[&str], value: Expr) -> Expr {
intent(
"edit-field",
Origin::human(7),
vec![
("target", base.clone()),
(
"path",
Expr::List(
path.iter()
.map(|segment| Expr::Symbol(Symbol::new(*segment)))
.collect(),
),
),
("value", value),
],
)
}
#[test]
fn registration_claims_the_exact_study_shape_and_one_codec() {
let mut cx = test_cx();
let mut registry = LensRegistry::new();
register_interference_surface(&mut cx, &mut registry).unwrap();
assert!(
registry
.surface_codec(&surface_interference_codec_symbol())
.is_some()
);
assert_eq!(registry.lenses().len(), 1);
assert_eq!(
registry.lenses()[0].meta.id.as_qualified_str(),
INTERFERENCE_SURFACE_CODEC_ID
);
let study_symbol = Symbol::new("ranked-study");
let study_value = cx
.factory()
.opaque(Arc::new(StudyDescriptor::example()))
.unwrap();
cx.env_mut().define(study_symbol.clone(), study_value);
let value = Expr::Symbol(study_symbol);
let outcome = registry
.dispatch_view(&mut cx, &value, &DispatchContext::permissive(&grant_all))
.unwrap();
assert_eq!(outcome.lens_id, surface_interference_codec_symbol());
assert!(matches!(outcome.reason, DispatchReason::ShapeMatch(100)));
}
fn grant_all(_: &CapabilityName) -> bool {
true
}
#[test]
fn certified_scene_contains_controls_summaries_heatmap_badges_and_cross_section() {
let mut cx = test_cx();
let value = study_expr(&mut cx);
let scene = InterferenceSurfaceCodec::new()
.encode(&mut cx, &value, &surface::preset("desktop").unwrap())
.unwrap();
sim_lib_scene::validate_scene(&scene).unwrap();
for kind in ["field", "slider", "heatmap", "badge", "plot"] {
assert!(contains_kind(&scene, kind), "missing scene/{kind}");
}
for text in [
"interference-controls",
"interference-sources",
"interference-evidence",
"projection-certificate",
"sampling",
"contrast",
] {
assert!(format!("{scene:?}").contains(text), "missing {text}");
}
}
#[test]
fn surface_budget_is_met_only_through_domain_detector_reduction() {
let mut cx = test_cx();
let value = study_expr(&mut cx);
let mut caps = surface::preset("desktop").unwrap();
set_display_limit(&mut caps, "heatmap-max-cells", 1);
set_display_limit(&mut caps, "heatmap-max-bytes", 4096);
let scene = InterferenceSurfaceCodec::new()
.encode(&mut cx, &value, &caps)
.unwrap();
let heatmap = find_kind(&scene, "heatmap").unwrap();
assert_eq!(number_field(heatmap, "rows"), 1);
assert_eq!(number_field(heatmap, "cols"), 1);
let certificate = access::field(heatmap, "projection-certificate").unwrap();
assert_eq!(
access::field_sym(certificate, "detector")
.unwrap()
.name
.as_ref(),
"detector-scalar-area-mean"
);
assert_eq!(number_field(certificate, "footprint-max-rows"), 2);
assert!(
access::field_str(heatmap, "advisory")
.unwrap()
.contains("sampling")
);
}
#[test]
fn project_and_model_edits_preserve_real_base_proposed_and_authority() {
let mut cx = test_cx();
install_interference_records(&mut cx).unwrap();
let codec = InterferenceSurfaceCodec::new();
let base = study_expr(&mut cx);
let project = codec
.decode(
&mut cx,
&base,
&edit(&base, &["observable"], Expr::Symbol(Symbol::new("phase"))),
)
.unwrap();
assert!(project.committable);
assert_eq!(project.base, base);
assert_ne!(project.proposed, project.base);
let operation = codec.commit(&mut cx, &project).unwrap();
assert_form_head(&operation.form, "interference", "project");
assert_eq!(
operation.required_capabilities[0].as_str(),
INTERFERENCE_PROJECT_CAPABILITY
);
assert!(operation.result_shape.is_some());
let model = codec
.decode(
&mut cx,
&base,
&edit(&base, &["frequency"], build::float(2_000.0)),
)
.unwrap();
assert!(model.committable);
let operation = codec.commit(&mut cx, &model).unwrap();
assert_form_head(&operation.form, "interference", "solve");
assert_eq!(
operation.required_capabilities[0].as_str(),
INTERFERENCE_SOLVE_CAPABILITY
);
assert!(operation.result_shape.is_some());
}
#[test]
fn every_declared_edit_field_classifies_to_the_existing_domain_operation() {
let mut cx = test_cx();
let codec = InterferenceSurfaceCodec::new();
let base = study_expr(&mut cx);
let project_cases = [
("observable", Expr::Symbol(Symbol::new("real"))),
("wt", build::float(0.5)),
("floor", build::float(0.1)),
("palette", Expr::Symbol(Symbol::new("blue-red"))),
("cross-section", build::uint(0)),
];
for (field, value) in project_cases {
let draft = codec
.decode(&mut cx, &base, &edit(&base, &[field], value))
.unwrap();
assert!(draft.committable, "{field}: {:?}", draft.diagnostics);
assert_form_head(
&codec.commit(&mut cx, &draft).unwrap().form,
"interference",
"project",
);
}
let model_cases = [
(vec!["frequency"], build::float(1_100.0)),
(vec!["medium", "attenuation-np-m"], build::float(0.01)),
(vec!["source", "source", "phase-rad"], build::float(0.25)),
(vec!["plane", "extent-u-m"], build::float(0.2)),
];
for (path, value) in model_cases {
let draft = codec
.decode(&mut cx, &base, &edit(&base, &path, value))
.unwrap();
assert!(draft.committable, "{path:?}: {:?}", draft.diagnostics);
assert_form_head(
&codec.commit(&mut cx, &draft).unwrap().form,
"interference",
"solve",
);
}
}
#[test]
fn invalid_edits_are_rejected_and_cannot_commit() {
let mut cx = test_cx();
let codec = InterferenceSurfaceCodec::new();
let base = study_expr(&mut cx);
let draft = codec
.decode(
&mut cx,
&base,
&edit(&base, &["frequency"], build::float(-1.0)),
)
.unwrap();
assert!(!draft.committable);
assert!(draft.diagnostics[0].message.contains("positive"));
assert!(codec.commit(&mut cx, &draft).is_err());
let stale_target = Expr::String("different Study".to_owned());
let submitted = intent(
"set-param",
Origin::human(8),
vec![
("target", stale_target),
("param", Expr::Symbol(Symbol::new("wt"))),
("value", build::float(0.5)),
],
);
let stale = codec.decode(&mut cx, &base, &submitted).unwrap();
assert!(!stale.committable);
assert!(stale.diagnostics[0].message.contains("does not match"));
}
#[test]
fn phase_masks_and_annotated_aliasing_remain_visible() {
let mut cx = test_cx();
let mut study = StudyDescriptor::example();
let problem = study.problem.to_problem().unwrap();
let plane = study.plane.to_plane().unwrap();
let thresholds = SamplingThresholds::new(10_000.0, 9_000.0, 0.0, 0.0).unwrap();
let sampling =
SamplingCertificate::measure_with_thresholds(&problem, &plane, thresholds).unwrap();
study.evidence.sampling = SamplingCertificateDescriptor::from_certificate(sampling);
study.evidence.sampling_policy = Symbol::qualified("interference", "annotate");
let study =
StudyDescriptor::new(study.problem, study.plane, study.field, study.evidence).unwrap();
let scene = study_scene_with(
&mut cx,
&study,
&surface::preset("desktop").unwrap(),
ProjectionOptions {
observable: Observable::Phase,
phase_floor: 0.8,
palette: "cyclic-phase",
cross_section: Some(0),
},
1,
None,
)
.unwrap();
let heatmap = find_kind(&scene, "heatmap").unwrap();
let valid = access::field(heatmap, "valid").unwrap();
assert!(matches!(valid, Expr::List(items) if items.contains(&Expr::Bool(false))));
assert!(
access::field_str(heatmap, "advisory")
.unwrap()
.contains("aliased")
);
assert!(format!("{scene:?}").contains("sampling aliased"));
}
#[test]
fn animation_is_preprojected_bounded_and_deterministic() {
let mut cx = test_cx();
let codec = InterferenceSurfaceCodec::new();
let value = study_expr(&mut cx);
let caps = surface::preset("desktop").unwrap();
let first = codec.encode_animation(&mut cx, &value, &caps, 4).unwrap();
let second = codec.encode_animation(&mut cx, &value, &caps, 4).unwrap();
assert_eq!(first, second);
assert_eq!(first.len(), 4);
assert!(
codec
.encode_animation(&mut cx, &value, &caps, MAX_ANIMATION_FRAMES + 1)
.is_err()
);
}
#[test]
fn identical_study_and_caps_produce_identical_scene() {
let mut cx = test_cx();
let value = study_expr(&mut cx);
let caps = surface::preset("desktop").unwrap();
let codec = InterferenceSurfaceCodec::new();
assert_eq!(
codec.encode(&mut cx, &value, &caps).unwrap(),
codec.encode(&mut cx, &value, &caps).unwrap()
);
}
fn assert_form_head(expr: &Expr, namespace: &str, name: &str) {
let Expr::Call { operator, .. } = expr else {
panic!("operation is not an evaluable call: {expr:?}");
};
assert!(
matches!(operator.as_ref(), Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(namespace) && symbol.name.as_ref() == name),
"unexpected operation head: {operator:?}",
);
}
#[test]
fn checked_recipe_realizes_edits_and_bounds_device_variants() {
assert_eq!(
crate::cookbook::interference_study_demo().unwrap(),
include_str!("../recipes/01-basics/interference-study/expected.txt")
.lines()
.map(str::to_owned)
.collect::<Vec<_>>()
);
}
fn contains_kind(expr: &Expr, kind: &str) -> bool {
find_kind(expr, kind).is_some()
}
fn find_kind<'a>(expr: &'a Expr, expected: &str) -> Option<&'a Expr> {
if matches!(
sim_lib_scene::node_kind(expr),
Some(kind) if kind.namespace.as_deref() == Some("scene") && kind.name.as_ref() == expected
) {
return Some(expr);
}
match expr {
Expr::Map(entries) => entries.iter().find_map(|(key, value)| {
find_kind(key, expected).or_else(|| find_kind(value, expected))
}),
Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
items.iter().find_map(|item| find_kind(item, expected))
}
_ => None,
}
}
fn set_display_limit(caps: &mut sim_lib_view::SurfaceCaps, field: &str, value: u64) {
let Expr::Map(entries) = &mut caps.display else {
panic!("display caps must be a map")
};
if let Some((_, found)) = entries.iter_mut().find(
|(key, _)| matches!(key, Expr::Symbol(symbol) if symbol.namespace.is_none() && symbol.name.as_ref() == field),
) {
*found = build::uint(value);
} else {
entries.push((Expr::Symbol(Symbol::new(field)), build::uint(value)));
}
}
fn number_field(expr: &Expr, field: &str) -> u64 {
let Expr::Number(number) = access::field(expr, field).unwrap() else {
panic!("{field} is not a number")
};
number.canonical.parse().unwrap()
}