CRUMB a card from devarno-cloud

Navigation & Time-Travel Queries

tnp intermediate 6 min read

ELI5

The navigator is a film editor who can jump the playhead to any timecode and show you exactly what the scene looked like at that moment — or cut between two timecodes to show only what changed.

Technical Deep Dive

Class Diagram

classDiagram
class NavigationIndex {
-node_to_timestamp: HashMap~NodeID, u64~
-timestamp_to_nodes: HashMap~u64, Vec~NodeID~~
+index_node(NodeID, u64)
+get_timestamp(NodeID) Option~u64~
+nodes_at_timestamp(u64) Vec~NodeID~
}
class TimelineNavigator {
-index: NavigationIndex
+index_timeline(timeline: &Timeline)
+search_timestamp(timeline, target_ms) Result~Vec~NodeID~~
+state_at_time(timeline, timestamp_ms) Result~Vec~u8~~
+timeline_diff(timeline, start_ms, end_ms) Result~Vec~NodeID~~
+breadcrumb_trail(timeline) Vec~(u64, usize)~
}
TimelineNavigator "1" --> "1" NavigationIndex

index_timeline

Iterates timeline.nodes in insertion order, reads each node’s wall-clock from timeline.node_timestamps, and writes both directions into NavigationIndex:

  • node_to_timestamp: NodeID → ms (for reverse lookup)
  • timestamp_to_nodes: ms → Vec (for range queries)

Note: index_timeline must be called manually before using nodes_at_timestamp. There is no automatic sync.

State-at-Time Query

sequenceDiagram
participant Caller
participant Navigator as TimelineNavigator
participant Timeline
Caller->>Navigator: state_at_time(timeline, ts_ms)
Navigator->>Timeline: nodes_before_timestamp(ts_ms)
Timeline-->>Navigator: Vec<NodeID>
Navigator->>Navigator: XOR-fold bytes into 32-byte hash
Navigator-->>Caller: Ok(Vec<u8>) — 32-byte state fingerprint

Uses the same XOR-fold as Timeline::state_hash but over a timestamp-filtered subset of nodes.

timeline_diff

pub fn timeline_diff(&self, timeline, start_ms, end_ms) -> Result<Vec<NodeID>, String> {
let start_nodes = timeline.nodes_before_timestamp(start_ms)?;
let end_nodes = timeline.nodes_before_timestamp(end_ms)?;
Ok(end_nodes.iter().filter(|n| !start_nodes.contains(n)).cloned().collect())
}

Returns nodes that appear in the [0, end_ms] window but not in the [0, start_ms] window — i.e., operations applied between start_ms and end_ms.

pub fn breadcrumb_trail(&self, timeline) -> Vec<(u64, usize)> {
// One entry per distinct timestamp; value = cumulative node count before that tick

Each element is (timestamp_ms, node_count_before). Useful for rendering a progress bar or timeline scrubber showing how many operations existed at each recorded moment.

Key Terms

  • NavigationIndex → dual-direction timestamp↔NodeID map; must be explicitly populated via index_timeline
  • state_at_time → 32-byte XOR fingerprint of all nodes before a timestamp; deterministic given same insertion order
  • timeline_diff → nodes added between two timestamps; not a symmetric diff
  • breadcrumb_trail → ordered (ts, count) pairs for UI scrubbing

Q&A

Q: If two nodes are inserted at the same millisecond, does timestamp_to_nodes preserve both? A: Yes — timestamp_to_nodes maps to Vec<NodeID>, so multiple nodes sharing a timestamp all appear under the same key.

Q: Does state_at_time return an error for an empty timeline? A: No. nodes_before_timestamp returns Ok([]), and XOR-folding an empty list produces a 32-byte zero vector.

Q: Does breadcrumb_trail use NavigationIndex or read the Timeline directly? A: It reads timeline.node_timestamps directly — the NavigationIndex is not used. The index field is populated separately and serves nodes_at_timestamp only.

Examples

let mut nav = TimelineNavigator::new();
nav.index_timeline(&timeline);
// Reconstruct state hash at a specific moment
let hash = nav.state_at_time(&timeline, 1_700_000_000_000).unwrap();
// Find nodes added in a 5-second window
let delta = nav.timeline_diff(&timeline, 1_700_000_000_000, 1_700_000_005_000).unwrap();
// Build a scrubber for the UI
let trail = nav.breadcrumb_trail(&timeline);
// [(ts1, 0), (ts2, 1), (ts3, 2), ...]

neighbors on the map