fix(gui): close-path deadlock, slow window close, null-panel click NPE
Two live-session defects fixed (diagnosed from a frozen launcher
process via jstack + telemetry log):
- ViewPanel.stop(): interrupt the render thread BEFORE
transformExecutor.shutdownNow() and clear pendingPaints only after
the join. shutdownNow() cancelled queued paint tasks whose finally
countDown() then never ran, parking the render thread forever on
paintLatch.await() and hanging the EDT in renderThread.join() —
window unclosable, SIGKILL required.
- Slow close (~2.8 s on every demo): device poll threads sleep 2 s
and stop() did a bare join(1000), burning the full timeout on the
EDT. HeadTrackingManager/SpaceMouseManager now interrupt before
join. SpaceNavigatorHid polls (250 ms) before read() — Linux close()
does not wake a read blocked on a silent-at-rest device — and stop()
joins before closing the fd. HeadTracker.stop() closes before join.
- ViewPanel.stop() now stops GlobalIllumination: GI worker threads
used to leak across demos in the launcher JVM (~3 cores stolen from
the next demo). colorOf() instanceof-guards the SolidPolygon cast —
textured triangles in a GI scene caused a ClassCastException storm.
- Clicking a text editor threw NPE (this.viewPanel is null): demos
build scenes with a null ViewPanel for the headless golden harness,
but GuiComponent.mouseClicked dereferenced the field. Focus never
engaged (no red border) and the exception aborted the frame being
painted (black screen until camera move). MouseInteractionController
gains a 4-arg mouseClicked carrying the dispatching view's
KeyboardFocusStack; RenderingContext/ViewPanel forward it;
GuiComponent focuses via the stack (legacy 1-arg null-guarded).
Verified: 48/48 engine tests, demos ALL GOLDENS PASS; repro harness
closes in 64 ms where the old code hung or took 2.8 s; live user
check of editor focus.