sim-lib-web-bridge 0.1.3

Session and transport bridge over realize/EvalFabric for the Intent/Scene bus.
Documentation
use sim_kernel::Expr;
use sim_lib_scene::{Anchor, AnchorSpace, GlanceCard, GlanceMetric, Transform3};
use sim_lib_view::SurfaceCaps;
use sim_lib_view_device::GlanceState;
use sim_lib_view_spatial::PoseView;
use sim_value::{access, build};

use crate::{HaloPreviewClient, VitureSceneClient};

#[test]
fn spatial_reprojects_and_glance_renders() {
    let caps = SurfaceCaps::from_preset("glasses-luma-ultra", "viture.browser").unwrap();
    let mut viture = VitureSceneClient::new(&caps, 12).unwrap();
    viture.receive(spatial_scene()).unwrap();

    let first = viture
        .frame(
            &PoseView::identity(1)
                .with_timing(1, 40_000_000)
                .with_translation([0.2, 0.0, 0.0])
                .with_angles(10.0, 0.0, 0.0),
        )
        .unwrap();
    let second = viture
        .frame(
            &PoseView::identity(2)
                .with_timing(2, 4_000_000)
                .with_translation([0.0, 0.0, 0.0]),
        )
        .unwrap();

    assert_eq!(viture.content_receipts(), 1);
    assert_eq!(viture.viewport().per_eye_px(), [1920, 1200]);
    assert_eq!(viture.viewport().frame_px(), [3840, 1200]);
    assert_scene_kind(first.as_ref(), "stereo");
    assert_scene_kind(second.as_ref(), "stereo");
    assert_eq!(field_u64(first.as_ref(), "predict-ms"), Some(12));
    assert_eq!(field_pair(first.as_ref(), "eye-px"), Some([1920, 1200]));
    assert_eq!(field_pair(first.as_ref(), "frame-px"), Some([3840, 1200]));
    assert_eq!(eye_children(first.as_ref(), "left-eye").len(), 1);
    assert_eq!(eye_children(first.as_ref(), "right-eye").len(), 1);
    assert_ne!(first, second, "local pose updates must move the eye roots");

    let held = viture
        .frame(&PoseView::identity(3).with_timing(13, 80_000_000))
        .unwrap();
    assert_eq!(held, second, "pose beyond the clamp holds the last frame");

    let mirror_caps = SurfaceCaps::from_preset("glasses-stereo", "viture.mirror").unwrap();
    let mut mirror = VitureSceneClient::new(&mirror_caps, 12).unwrap();
    let mirror_scene = spatial_scene();
    mirror.receive(mirror_scene.clone()).unwrap();
    assert!(mirror.is_mirror());
    assert_eq!(
        mirror.frame(&PoseView::identity(4)).unwrap().as_ref(),
        &mirror_scene
    );

    let halo_caps = SurfaceCaps::from_preset("glasses-hud", "halo.preview").unwrap();
    let mut halo = HaloPreviewClient::new(&halo_caps).unwrap();
    halo.receive(glance_scene()).unwrap();
    let card = halo.frame(&GlanceState::idle(1)).unwrap();
    assert_eq!(halo.content_receipts(), 1);
    assert_scene_kind(card.as_ref(), "glance");
    assert_eq!(
        GlanceCard::from_scene(card.as_ref()).unwrap().title,
        "Build"
    );
}

#[test]
fn glasses_clients_fail_closed_on_wrong_scene_kinds() {
    let viture_caps = SurfaceCaps::from_preset("glasses-luma-ultra", "viture.browser").unwrap();
    let mut viture = VitureSceneClient::new(&viture_caps, 12).unwrap();
    assert!(viture.receive(glance_scene()).is_err());
    assert!(viture.frame(&PoseView::identity(1)).is_err());

    let halo_caps = SurfaceCaps::from_preset("glasses-hud", "halo.preview").unwrap();
    let mut halo = HaloPreviewClient::new(&halo_caps).unwrap();
    assert!(halo.receive(spatial_scene()).is_err());
    assert!(halo.frame(&GlanceState::idle(1)).is_err());
}

fn spatial_scene() -> Expr {
    sim_lib_scene::spatial(vec![sim_lib_scene::panel(
        "workspace",
        sim_lib_scene::node("text", vec![("text", build::text("Workspace"))]),
        Anchor::new(AnchorSpace::World, "desk"),
        Transform3::new([0.0, 0.0, -1.5], [0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 1.0]),
    )])
}

fn glance_scene() -> Expr {
    GlanceCard::new(
        "Build",
        Some(GlanceMetric::new("tests", "green")),
        None,
        "info",
        2,
    )
    .to_scene()
}

fn assert_scene_kind(expr: &Expr, expected: &str) {
    let kind = sim_lib_scene::node_kind(expr).expect("scene kind");
    assert_eq!(kind.namespace.as_deref(), Some("scene"));
    assert_eq!(kind.name.as_ref(), expected);
}

fn eye_children<'a>(scene: &'a Expr, field: &str) -> &'a [Expr] {
    match access::field(access::field(scene, field).unwrap(), "children").unwrap() {
        Expr::List(children) => children,
        other => panic!("expected children list, got {other:?}"),
    }
}

fn field_pair(scene: &Expr, field: &str) -> Option<[u64; 2]> {
    let Expr::List(values) = access::field(scene, field)? else {
        return None;
    };
    Some([number(&values[0])?, number(&values[1])?])
}

fn field_u64(scene: &Expr, field: &str) -> Option<u64> {
    number(access::field(scene, field)?)
}

fn number(expr: &Expr) -> Option<u64> {
    let Expr::Number(number) = expr else {
        return None;
    };
    number.canonical.parse().ok()
}