Skip to content

Re-look at #34 click-to-focus in light of #55: is the wheel-path stray node click the same hard-coded-coordinate capture? #58

Description

@mohidmakhdoomi

Goal

Re-examine the click-to-focus test (tests/e2e/matrix.spec.ts, "click-to-focus fixes the node, animates the camera, and reset restores the view") in light of #55, which proved that a stray node capture at a hard-coded screen coordinate — not browser/rasterizer nondeterminism — was the real mechanism behind a long-standing "flake".

Two questions, in order:

  1. Does Firefox e2e flake: "background drag" rotation (matrix.spec.ts:224) — synthetic-input-delivery nondeterminism (survives on hardware) #55's mechanism also explain this test's stray-click recovery loop? (Primary — new information, concrete and testable.)
  2. Is the residual defensive scaffolding still earning its keep now that Flaky CI e2e: click-to-focus (matrix.spec.ts:235) intermittently fails on SwiftShader (~50% observed 2026-07-20) #34's hover-first fix has landed and Firefox e2e flake: "background drag" rotation (matrix.spec.ts:224) — synthetic-input-delivery nondeterminism (survives on hardware) #55 has retired the sibling folk diagnosis? (Secondary — cleanup, gated on the answer to 1.)

This issue is not a claim that #34 is unfixed. #34 received a genuine root-cause fix (65a1ad7, 2026-07-20): the library resolves hover only in the render loop's throttled raycast and defers onClick a frame past pointerup, so under software WebGL a bare click could resolve against a not-yet-committed hover; settleHoverThenClick forces a committed hover raycast before pointerdown. That mechanism is documented in tests/e2e/pointer.ts and is not in question here.

Motivation

#55's finding. The Firefox background-drag flake was blamed for years on "synthetic-input-delivery nondeterminism". Instrumentation refuted that (12/12 pointermoves delivered in every failure) and proved H1 stray node capture: the hard-coded drag start (150, 450) intermittently landed on a node whose screen projection had grown after the wheel-zoom, DragControls grabbed it, and the Trackball was disabled. Rasterizer-independent (CPU-side three.js raycast → survived on RTX 3080), per-run-random (random layout seed), Firefox-dominant (zooms ~18–22% closer → bigger projections).

The link to this test. This test contains its own unexplained stray-node problem, and it is structured around a hard-coded screen coordinate in a moving scene — the exact anti-pattern #55 fixed. From matrix.spec.ts:

// Wheel from a corner: while zooming through the rotating cloud, nodes sweep
// under the pointer, and the hover pipeline can otherwise register a stray
// node click that stops rotation and fixes a node prematurely.
await page.mouse.move(60, 540);

and, immediately after:

// The wheel path itself occasionally registers a stray node interaction
// (observed in both engines) that fixes a node and stops rotation. [...]
// on a stray, restart from a fresh page instead of aiming into a corrupted state.

That is a static pointer, moving scene variant of #55's static coordinate, changed scene. The parked corner (60, 540) is asserted-empty by nothing; the loop then wheels the camera 60 bursts closer, growing every node's projection, while the cloud auto-rotates. Nodes sweeping under a parked pointer is precisely how a hover-pipeline stray click would arise. The current handling is a recovery loop (detect fixedNodeCount !== 0, full page reload, retry, up to 2×) — i.e. we detect and recover from the symptom without having proven the cause.

Why now. #55 left behind exactly the tooling to settle this, deliberately retained for future background-point triage:

  • nodeOccupancyAtPoint / __graphNodeOccupancyAtPoint in tests/e2e/graph-handle.ts — the DragControls-mirroring hit test that was the H1 discriminator.
  • pickBackgroundDragPoint — max-edge-clearance point selection with a pixel-margin floor.
  • The out-of-tree amplified-reproduction harness under tests/diagnostics/55-drag/ (own playwright.diag.config.ts), a re-runnable template for this investigation.

Scope

Phase A — Instrument the stray (primary).

Phase B — Fix or justify (conditional on A).

  • If confirmed: replace the parked corner with a probe-verified clearance point maintained across the zoom (reuse/extend pickBackgroundDragPoint; the point must stay clear as projections grow, so re-probing between bursts may be needed). The recovery-reload loop then becomes dead code and can be retired.
  • If refuted: document the actual mechanism and keep the recovery loop, with the refutation recorded so the next reader does not re-open this.

Phase C — Re-qualify the residual scaffolding (secondary, gated on B).
Only after A/B settle, audit each item on evidence — retire what no longer earns its keep, keep and justify the rest:

Constraints and invariants

Definition of Done

Non-goals

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions