fix(ui-map): inverse stencil mask for the visibility fog (Safari pan perf, stage 2) #22

Merged
developer merged 1 commits from feature/ui-map-fog-inverse-mask into development 2026-05-20 15:02:03 +00:00
Owner

Problem

After stage 1 (#21, render-on-demand) the idle / whole-system freeze was
gone, but panning a loaded report (KNNTS041) with "visible hyperspace"
on was still heavy in Safari. The fog still cut its visibility holes by opaque
overpaint — ~260 near-world-sized opaque circles blended over the fog every
rendered frame, a fill-rate cliff for Safari's WebGPU / Apple's tile-based GPU.

Stage 2 (this PR) — inverse stencil mask

Replace the overpaint with an inverse stencil mask: setVisibilityFog
draws the FOG_COLOR rectangle(s) into fogLayer and collects the visibility
circles into one Graphics set as fogLayer.setMask({ mask, inverse: true }),
so the fog shows everywhere except the union of the circles.

  • Per-frame cost drops from dozens of blended opaque circle fills to one
    rectangle fill + a stencil pass (no colour writes) — cheap on Apple's TBDR
    GPU.
  • Stays fully vector: the fog edge is crisp at any zoom (no rasterisation).
  • fogPaintOps and its unit tests are unchanged — the circle ops now feed the
    mask instead of an overpaint.

Verification

  • Owner confirmed in Safari: panning is smooth, freezes completely gone.
  • High-contrast screenshot during development showed a correct circle-union
    hole cut out of the fog field.
  • svelte-check clean; Vitest 707/707; map e2e (fog toggle, render-on-demand,
    no-inertia, wrap) green on chromium + webkit.

Docs

  • ui/docs/renderer.md fog section (inverse-mask mechanism, rationale).
  • ui/PLAN.md Phase 29 decision 9.

🤖 Generated with Claude Code

## Problem After stage 1 (#21, render-on-demand) the idle / whole-system freeze was gone, but **panning** a loaded report (`KNNTS041`) with "visible hyperspace" on was still heavy in Safari. The fog still cut its visibility holes by opaque overpaint — ~260 near-world-sized opaque circles blended over the fog every rendered frame, a fill-rate cliff for Safari's WebGPU / Apple's tile-based GPU. ## Stage 2 (this PR) — inverse stencil mask Replace the overpaint with an **inverse stencil mask**: `setVisibilityFog` draws the `FOG_COLOR` rectangle(s) into `fogLayer` and collects the visibility circles into one `Graphics` set as `fogLayer.setMask({ mask, inverse: true })`, so the fog shows everywhere **except** the union of the circles. - Per-frame cost drops from dozens of blended opaque circle fills to one rectangle fill + a stencil pass (no colour writes) — cheap on Apple's TBDR GPU. - Stays **fully vector**: the fog edge is crisp at any zoom (no rasterisation). - `fogPaintOps` and its unit tests are unchanged — the circle ops now feed the mask instead of an overpaint. ## Verification - **Owner confirmed in Safari: panning is smooth, freezes completely gone.** - High-contrast screenshot during development showed a correct circle-union hole cut out of the fog field. - `svelte-check` clean; Vitest 707/707; map e2e (fog toggle, render-on-demand, no-inertia, wrap) green on chromium + webkit. ## Docs - `ui/docs/renderer.md` fog section (inverse-mask mechanism, rationale). - `ui/PLAN.md` Phase 29 decision 9. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
developer added 1 commit 2026-05-20 14:59:36 +00:00
fix(ui-map): cut the visibility fog with an inverse stencil mask (Safari pan perf, stage 2)
Tests · UI / test (push) Successful in 1m57s
Tests · UI / test (pull_request) Successful in 1m56s
a08f4f55b0
Stage 1 (render-on-demand) removed the idle / whole-system freeze, but
panning a loaded map with "visible hyperspace" on stayed heavy in Safari:
the fog still cut its visibility holes by opaque overpaint — on KNNTS041
that is ~260 near-world-sized opaque circles blended over the fog every
rendered frame, a fill-rate cliff for Safari's WebGPU / Apple's tile-based
GPU.

Replace the overpaint with an INVERSE stencil mask: setVisibilityFog now
draws the FOG_COLOR rectangle(s) into fogLayer and collects the visibility
circles into one Graphics set as fogLayer.setMask({ mask, inverse: true }),
so the fog shows everywhere except the union of the circles. Per-frame cost
drops from dozens of blended opaque circle fills to one rect fill + a
stencil pass (no colour writes), which Apple's TBDR GPU handles cheaply,
and the fog stays fully vector — crisp at any zoom.

fogPaintOps and its unit tests are unchanged (the circle ops now feed the
mask instead of an overpaint). Verified with a high-contrast screenshot
during development (fog field with a correct circle-union hole) plus the
existing fog / render-on-demand e2e green on chromium + webkit.

Docs: renderer.md fog section + PLAN.md Phase 29 decision 9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
developer merged commit 6572f5b59d into development 2026-05-20 15:02:03 +00:00
developer deleted branch feature/ui-map-fog-inverse-mask 2026-05-20 15:02:03 +00:00
Sign in to join this conversation.