sim/runtime/glasses/proofs/
two_rate.rs1use std::rc::Rc;
2
3use sim_kernel::{Error, Expr, Result};
4use sim_lib_scene::{Anchor, AnchorSpace, Transform3};
5use sim_lib_stream_device::ModeledSource;
6use sim_lib_stream_xr::{ModeledViturePoseSource, XrPoseSample};
7use sim_lib_view::{SurfaceCaps, SurfaceCodec};
8use sim_lib_view_device::{AdapterInput, DeviceSurfaceCapsExt, EncodedScene, FrameClock};
9use sim_lib_view_spatial::{PoseView, SpatialSurfaceCodec, viture_loop};
10use sim_value::build;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct TwoRateProof {
15 pub content_encodes: u64,
17 pub dropped: u32,
19 pub stale: bool,
21 pub clamped_predict_ms: u64,
23 pub held_after_clamp: bool,
25}
26
27impl TwoRateProof {
28 pub fn to_expr(&self) -> Expr {
30 build::map(vec![
31 ("kind", build::qsym("glasses/sdk", "two-rate-proof")),
32 ("content-encodes", build::uint(self.content_encodes)),
33 ("dropped", build::uint(u64::from(self.dropped))),
34 ("stale", Expr::Bool(self.stale)),
35 ("clamped-predict-ms", build::uint(self.clamped_predict_ms)),
36 ("held-after-clamp", Expr::Bool(self.held_after_clamp)),
37 ])
38 }
39}
40
41pub fn prove_two_rate() -> Result<TwoRateProof> {
43 let caps = SurfaceCaps::from_preset("glasses-luma-ultra", "sdk.viture")
44 .ok_or_else(|| Error::HostError("Viture surface preset missing".to_owned()))?;
45 let profile = caps.device_profile();
46 let mut cx = sim_kernel::testing::bare_cx();
47 let scene = SpatialSurfaceCodec::new().encode(&mut cx, &workspace_scene(), &caps)?;
48 let encoded = EncodedScene::new(scene);
49 let content_encodes = 1;
50 let source = ModeledViturePoseSource;
51 let (mut adapter, clock) = viture_loop(&profile, 12);
52
53 let mut newest = pose_view(&source.at(0), 1, 4_000_000);
54 for index in 0..4 {
55 newest = pose_view(&source.at(index), 1, 4_000_000);
56 adapter.offer(&newest);
57 }
58 let fresh = adapter.step(
59 &clock,
60 &AdapterInput::new(encoded.clone(), 41, newest, clock.tick),
61 &profile,
62 )?;
63
64 let stale_clock = FrameClock::new(4, profile.rate);
65 let stale_pose = pose_view(&source.at(5), 12, 40_000_000);
66 adapter.offer(&stale_pose);
67 let stale = adapter.step(
68 &stale_clock,
69 &AdapterInput::new(encoded.clone(), 41, stale_pose, 0),
70 &profile,
71 )?;
72
73 let beyond = pose_view(&source.at(6), 13, 80_000_000);
74 adapter.offer(&beyond);
75 let held = adapter.step(
76 &stale_clock,
77 &AdapterInput::new(encoded, 41, beyond, 0),
78 &profile,
79 )?;
80
81 Ok(TwoRateProof {
82 content_encodes,
83 dropped: fresh.dropped,
84 stale: stale.stale,
85 clamped_predict_ms: field_u64(stale.out.as_ref(), "predict-ms").unwrap_or(0),
86 held_after_clamp: Rc::ptr_eq(&stale.out, &held.out),
87 })
88}
89
90fn pose_view(sample: &XrPoseSample, age_ms: u64, predict_ns: u64) -> PoseView {
91 let mut pose = PoseView::identity(sample.seq()).with_timing(age_ms, predict_ns);
92 if let Some(position) = sample.position_m() {
93 pose = pose.with_translation(position);
94 }
95 pose
96}
97
98fn workspace_scene() -> Expr {
99 sim_lib_scene::spatial(vec![sim_lib_scene::panel(
100 "review",
101 sim_lib_scene::text_node("Modeled review"),
102 Anchor::new(AnchorSpace::World, "desk"),
103 Transform3::new([0.0, 0.0, -1.6], [0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 1.0]),
104 )])
105}
106
107fn field_u64(expr: &Expr, name: &str) -> Option<u64> {
108 let Expr::Number(number) = sim_value::access::field(expr, name)? else {
109 return None;
110 };
111 number.canonical.parse().ok()
112}