fix(ui-map): cut the visibility fog with an inverse stencil mask (Safari pan perf, stage 2)
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>
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
// Phase 29 unit coverage for the visible-hyperspace overlay's
|
||||
// layered overpaint logic. `fogPaintOps` lives in `src/map/render.ts`
|
||||
// next to its sole consumer (`RendererHandle.setVisibilityFog`) —
|
||||
// the renderer dispatches each op onto its own Pixi `Graphics`
|
||||
// inside a `fogLayer` container that sits below every primitive
|
||||
// copy. The natural rendering order paints fog underneath the
|
||||
// world, replacing the earlier `cut()` implementation that
|
||||
// produced disconnected arc segments.
|
||||
// Phase 29 unit coverage for the visible-hyperspace overlay's paint
|
||||
// ops. `fogPaintOps` lives in `src/map/render.ts` next to its sole
|
||||
// consumer (`RendererHandle.setVisibilityFog`): the renderer draws
|
||||
// the rectangle ops into a `fogLayer` container (below every
|
||||
// primitive copy) and feeds the circle ops into an inverse stencil
|
||||
// mask that cuts the visibility holes out of the fog. `fogPaintOps`
|
||||
// only produces the ordered op list — rect(s) first, then one circle
|
||||
// per visibility circle — which is what these tests pin; earlier
|
||||
// renderer implementations used Pixi `cut()` (disconnected arcs) and
|
||||
// then opaque overpaint (a fill-rate cliff under Safari's WebGPU).
|
||||
//
|
||||
// Coordinates returned by `fogPaintOps` are in world space because
|
||||
// `fogLayer` has no transform — wraps for torus mode are baked
|
||||
// into the ops directly.
|
||||
// `fogLayer` and the mask have no transform — wraps for torus mode
|
||||
// are baked into the ops directly.
|
||||
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user