Time-synchronized joins ======================= By default a multi-parent join groups its inputs by **lineage**: the halves it combines must descend from the same originating message of one producer — a diamond that fans out and reconverges (``mode='trace'``). That is the right model when the branches share an upstream ancestor. To fuse streams from **independent** producers — several cameras plus sensors, none sharing an upstream — group by **event time** instead:: from videoflow.core.policies import JoinPolicy fused = FusionProcessor(name='fuse', join_policy=JoinPolicy( mode='time', # group by event_ts, not trace lineage tolerance_ms=8, # messages within 8ms are the same moment (< one 60fps frame) timeout_seconds=0.05, # lateness bound: how long to wait for stragglers quorum=6, # emit once >= 6 of N cameras are present (missing ones -> None) collect={'imu': 25}, # high-rate parent: deliver every sample within 25ms as a list ))(cam1, cam2, cam3, cam4, cam5, cam6, cam7, cam8, imu) Event timestamps ---------------- Each input carries an **event timestamp** (epoch seconds) that a producer stamps and that travels with the message through the whole flow — downstream nodes inherit it automatically. A producer stamps it via ``ctx.set_event_timestamp(ts)``; the built-in ``VideostreamReader`` does this per frame (``timestamp_source='clock'`` for live streams, ``'position'`` for synchronized recordings). A fusion node reads each input's exact time from ``ctx.input_info`` (per-parent ``event_ts`` / ``metadata``) so it can interpolate between samples of a high-rate parent. Cross-device time accuracy itself is an operations concern — genlocked cameras and PTP/NTP-disciplined hosts — the framework aligns on whatever timestamps it is given. Scaling ------- A time-aligned join runs with ``nb_tasks=1``: every parent's half must reach the same worker to be grouped. Scale the per-stream work in the nodes **upstream** of the fusion node instead. Backward compatibility ---------------------- ``mode='trace'`` is the default and never reads ``event_ts``, so existing flows — including ones whose producers stamp no time at all — behave exactly as before. A producer that never calls ``ctx.set_event_timestamp`` still gets an event time on the wire: its publish wall-clock, which trace-mode joins ignore and which serves as a sensible fallback if such a stream is later fed into a ``mode='time'`` join. See ``examples/multicamera_time_sync.py`` for a runnable multi-camera fusion flow.