Skip to content

Perf S2: viewport culling (zoomed-in) + throttled minimap - #69

Merged
mvalancy merged 1 commit into
developfrom
perf/large-graph-s2-cull
Jun 15, 2026
Merged

Perf S2: viewport culling (zoomed-in) + throttled minimap#69
mvalancy merged 1 commit into
developfrom
perf/large-graph-s2-cull

Conversation

@mvalancy

Copy link
Copy Markdown
Member

Stage 2 of the measured large-graph performance effort (follows #68)

When you zoom into a large graph, most nodes are off-screen but still painted
every frame. This adds geometric viewport culling: off-screen node groups
(and edges with both ends off-screen) get display:none, so they're neither laid
out nor painted.

Do-no-harm gating (driven by measurement)

A first attempt culled unconditionally and slowed zoom — because at the
whole-graph "fit" view (scale ~0.1) every node is on screen, so a cull pass is
pure overhead. So culling now:

  • only runs above scale 0.5 (zoomed in), and reveals everything once when you
    zoom back out;
  • only enables above 200 nodes;
  • recomputes on a throttle during sim ticks and on every pan/zoom (the
    one-shot sim is usually stopped, so the zoom handler is what reveals nodes
    panned back into view).

Also throttles the minimap position-dict rebuild (every tick → every 8th).

Result (Compute Core, 1000n/1400e, HIGH)

scenario before after
zoomed-in drag (scale ~1.7, 982 culled) 1.2 9
whole-graph fit-view drag/zoom 1.2 / 3.5 ~unchanged (do-no-harm)
idle 60 60 (S1 preserved)

The whole-graph view (scale ~0.1, all 40k SVG elements painted) is bound by
element count, not culling — that's the next stage (simplified-node LOD at low
zoom), which targets the FPS you feel when looking at the entire graph.

Verification

  • web typecheck: 0 errors
  • THE GATE (test:smoke) 5/5
  • hierarchy-navigation green (exercises the 1000-node Compute Core)
  • large-graph-profile.spec.ts extended with a zoomed-in drag + culled-count assertion

🤖 Generated with Claude Code

When zoomed into a large graph, most nodes are off-screen yet still painted
every frame — off-screen SVG costs the same to paint as on-screen. Add geometric
viewport culling: node groups (and edges with both ends hidden) outside the
viewport + margin get display:none, so they are neither laid out nor painted.

Do-no-harm gating (learned by measurement): culling is skipped below scale 0.5
(the whole-graph "fit" view, where every node is on screen and a cull pass is
pure overhead — an early attempt that culled unconditionally slowed zoom). When
zoomed back out below the threshold, everything is revealed once. Culling is only
enabled above 200 nodes. Recomputed on a throttle during sim ticks AND on every
pan/zoom (the one-shot sim is usually stopped, so the zoom handler is the only
thing that can reveal nodes panned back into view).

Also throttle the minimap position-dict rebuild (every tick -> every 8th); it
doesn't need 60 Hz and was rebuilding a full 1000-entry dict per tick.

Measured (Compute Core, 1000n/1400e, HIGH), zoomed in to scale ~1.7 (982 nodes
culled): zoomed-in drag FPS 1.2 -> 9 (~8x). Whole-graph fit-view drag/zoom
unchanged (do-no-harm); idle still 60 (S1 preserved). The whole-graph view
(scale ~0.1, all elements painted) is bound by element count, addressed next by
simplified-node LOD.

large-graph-profile.spec.ts now also measures a zoomed-in drag + culled count.

Verified: web typecheck 0; THE GATE 5/5; hierarchy-navigation green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Comprehensive Test Suite

  • Unit suites (Node 18.x & 20.x) — core, web, server, mcp-server: ✅ passed
  • Installer & deploy config: ✅ passed

Full-stack smoke gate runs in the CI workflow.

@mvalancy
mvalancy merged commit 19e7bec into develop Jun 15, 2026
16 checks passed
@mvalancy
mvalancy deleted the perf/large-graph-s2-cull branch June 15, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant