@@ -172,3 +172,54 @@ def mid_occ_frac(coverage: float) -> float:
172172 fracs = [mid_occ_frac (c ) for c in (0.5 , 1.0 , 2.0 )]
173173 assert fracs == sorted (fracs ) # monotonic non-decreasing
174174 assert fracs [- 1 ] > 0.0
175+
176+
177+ def _scene_with_occlusion (occ : Occlusion , * , num_frames : int = 50 ) -> dict :
178+ scene = (
179+ SceneBuilder (fps = 30.0 , num_frames = num_frames )
180+ .cameras (
181+ CameraRig .ring (
182+ n = 3 ,
183+ radius = 4.0 ,
184+ height = 1.5 ,
185+ look_at = (0.0 , 0.0 , 0.5 ),
186+ focal = 800.0 ,
187+ width = 640 ,
188+ height_px = 480 ,
189+ )
190+ )
191+ .entity ("obj" , Path .linear ((0.0 , - 0.6 , 0.5 ), (0.0 , 0.6 , 0.5 )))
192+ .occlude (occ )
193+ .build ()
194+ )
195+ return build_manifest (scene )
196+
197+
198+ def test_during_seconds_matches_during_frames () -> None :
199+ """seconds window (0.5, 1.5) @ 30fps rounds to frames (15, 45) and produces
200+ the identical per-frame visible pattern for the occluded point."""
201+ by_frames = _scene_with_occlusion (Occlusion .sphere (size = 0.15 ).blocks (camera = 1 ).during ((15 , 45 )))
202+ by_seconds = _scene_with_occlusion (
203+ Occlusion .sphere (size = 0.15 ).blocks (camera = 1 ).during_seconds (0.5 , 1.5 )
204+ )
205+ vis_frames = [
206+ fr ["points" ]["center" ]["per_cam" ][1 ]["visible" ] for fr in by_frames ["entities" ][0 ]["frames" ]
207+ ]
208+ vis_seconds = [
209+ fr ["points" ]["center" ]["per_cam" ][1 ]["visible" ]
210+ for fr in by_seconds ["entities" ][0 ]["frames" ]
211+ ]
212+ assert vis_frames == vis_seconds
213+
214+
215+ def test_during_seconds_rejects_inverted_window () -> None :
216+ with pytest .raises (ValueError ):
217+ Occlusion .sphere (size = 0.15 ).during_seconds (1.5 , 0.5 )
218+
219+
220+ def test_occlusion_rejects_both_frames_and_seconds_windows () -> None :
221+ """An occlusion must declare exactly one schedule: frames or seconds."""
222+ with pytest .raises (ValueError ):
223+ Occlusion .sphere (size = 0.15 ).during ((3 , 7 )).during_seconds (0.5 , 1.5 )
224+ with pytest .raises (ValueError ):
225+ Occlusion .sphere (size = 0.15 ).during_seconds (0.5 , 1.5 ).during ((3 , 7 ))
0 commit comments