Problem Statement
Painting thousands of active verification nodes and their consensus channels dynamically using standard HTML elements causes massive browser DOM slowdowns. The current implementation renders each node as a React component wrapped in a <div> with inline styles for positioning; at 5,000+ nodes the DOM tree exceeds 15,000 elements (nodes + edges + labels), triggering layout thrashing and frame drops below 15 FPS during pan/zoom interactions.
Technical Bounds & Invariants
- Target node count: 10,000 concurrent validators rendered at once
- Minimum acceptable frame rate: 30 FPS during pan/zoom
- Maximum memory budget for topology: 200 MB (browser tab limit)
- Layout algorithm: force-directed graph with physics simulation at 60 updates/sec
- Edge lines must show directional consensus flow (arrows) and latency color coding
Codebase Navigation Guide
- Primary target:
/src/components/network/NodeTopologyMap.tsx
- Current (slow) SVG-based rendering:
/src/components/network/NetworkGraph.tsx
- Node data input:
/src/hooks/useNodeTopology.ts — returns { nodes: Node[], edges: Edge[] }
- Layout engine:
/src/lib/forceLayout.ts — d3-force simulation
Step-by-Step Resolution Blueprint
- Replace the SVG/DOM rendering path with an HTML5 Canvas overlay using
requestAnimationFrame for the render loop; implement a TopologyCanvas class that maintains a layer separation: (a) background grid layer, (b) edge layer, (c) node layer, (d) selection highlight layer
- Implement a spatial hash grid (cell size = 100px) to accelerate hit-testing on click/hover: on mousemove, check only the 3x3 cell neighborhood around the cursor position against the spatial hash; this reduces click detection from O(n) to O(1) for 10,000 nodes
- Port the d3-force simulation to run inside a Web Worker (
/src/workers/forceLayout.worker.ts) using OffscreenCanvas for non-blocking physics ticks; transfer node/edge positions back to the main thread via postMessage at 30 FPS (every 2nd animation frame)
- For edge rendering, use a
Path2D cache: recompute bezier curves only when topology changes (node add/remove), not every frame; store cached paths in a Map<edgeId, Path2D> and draw with ctx.stroke(cachedPath)
- Implement a dynamic level-of-detail system: nodes farther than 50% from viewport center render as 2px circles (no label); within 25% they render with full labels, status rings, and connection ports; use
ctx.measureText only for visible-range nodes
- Add a WebGL fallback path using
regl or pixi.js when navigator.gpu is available: encode node positions into a float32 vertex buffer and render as instanced point sprites for >5,000 nodes
- Write a performance regression test in Playwright that captures
requestAnimationFrame timestamps during a 30s simulated topology with 10,000 nodes and asserts p95 frame time < 33ms (30 FPS)
Problem Statement
Painting thousands of active verification nodes and their consensus channels dynamically using standard HTML elements causes massive browser DOM slowdowns. The current implementation renders each node as a React component wrapped in a
<div>with inline styles for positioning; at 5,000+ nodes the DOM tree exceeds 15,000 elements (nodes + edges + labels), triggering layout thrashing and frame drops below 15 FPS during pan/zoom interactions.Technical Bounds & Invariants
Codebase Navigation Guide
/src/components/network/NodeTopologyMap.tsx/src/components/network/NetworkGraph.tsx/src/hooks/useNodeTopology.ts— returns{ nodes: Node[], edges: Edge[] }/src/lib/forceLayout.ts— d3-force simulationStep-by-Step Resolution Blueprint
requestAnimationFramefor the render loop; implement aTopologyCanvasclass that maintains a layer separation: (a) background grid layer, (b) edge layer, (c) node layer, (d) selection highlight layer/src/workers/forceLayout.worker.ts) usingOffscreenCanvasfor non-blocking physics ticks; transfer node/edge positions back to the main thread viapostMessageat 30 FPS (every 2nd animation frame)Path2Dcache: recompute bezier curves only when topology changes (node add/remove), not every frame; store cached paths in aMap<edgeId, Path2D>and draw withctx.stroke(cachedPath)ctx.measureTextonly for visible-range nodesreglorpixi.jswhennavigator.gpuis available: encode node positions into a float32 vertex buffer and render as instanced point sprites for >5,000 nodesrequestAnimationFrametimestamps during a 30s simulated topology with 10,000 nodes and asserts p95 frame time < 33ms (30 FPS)