Files
notescraft/scripts/waves/wave1/graph.md
T
2026-10-13 18:34:18 -03:00

2.2 KiB

Crate: crates/graph (notescraft-graph, layer L2). SPEC sections: 6 item 5 and 12 (graph budget). Allowed new dependencies: thiserror only. Pure std, no egui, no rayon unless it is already a workspace dependency (it is not: do not add it).

Implement a deterministic force-directed layout:

  1. Graph { node_count: usize, edges: Vec<(usize, usize)> } with Graph::new(node_count, edges) -> Result<Graph> (validates edge indices, drops self loops and duplicates, caps node_count at 200_000).
  2. LayoutConfig { iterations, repulsion, attraction, gravity, cooling, area, seed, pinned: Vec<(usize, [f32;2])> } with Default, and layout(&Graph, &LayoutConfig) -> Vec<[f32; 2]>. Fruchterman-Reingold with a uniform-grid spatial hash so repulsion only considers nearby nodes (cutoff radius 2k), gravity toward the centre, linear cooling. Initial positions from a seeded xorshift64 RNG (no rand crate): same seed gives bit-identical output on every platform (use only +,-,*,/,sqrt on f32 in a fixed order; no fused ops, no HashMap iteration order dependence). Pinned nodes never move. NaN/inf can never be produced (clamp).
  3. Incremental use: Layout::new(&Graph, &LayoutConfig) with step(&mut self) -> f32 (returns remaining energy) and positions(), so a UI can animate; layout() is just run-to-completion. Layout::pin, unpin, set_position.
  4. local_graph(&Graph, center: usize, depth: usize) -> (Graph, Vec<usize>) returning the induced subgraph within depth hops (depth 1..=3 typical, any depth capped) plus the mapping to original ids.
  5. Helpers for the renderer: degrees(&Graph) -> Vec<u32>, components(&Graph) -> Vec<usize> (connected component id per node, iterative, no recursion), orphans(&Graph).

Tests: determinism (two runs identical, different seeds differ), no NaN on degenerate inputs (0 nodes, 1 node, all nodes connected to one hub, duplicate edges, disconnected components), pinned nodes stay, edge validation errors, local_graph depth correctness, components. Performance test (marked #[ignore] but you must run it once with cargo test -p notescraft-graph --release -- --ignored): 5000 nodes / 15000 random edges (deterministic generator) lays out in under 2 seconds in release; report the measured time.