Files
worldmonitor/todos/159-pending-p2-globe-flush-polygons-scenario-precompute.md
Elie Habib 60e727679c feat(supply-chain): Sprint E — scenario visual completion + service parity (#2910)
* feat(supply-chain): Sprint E — scenario visual completion + service parity

- E1: fetchSectorDependency exported from supply-chain service index
- E2: PRO gate + all-renderer dispatch in MapContainer.activateScenario
- E3: scenario summary banner in SupplyChainPanel (dismiss wired)
- E4: "Simulate Closure" trigger button in expanded chokepoint cards
- E5: affectedIso2s heat layer in DeckGLMap (GeoJsonLayer, red tint)
- E6: SVG renderer setScenarioState (best-effort iso2 fill)
- E7: Globe renderer scenario polygons via flushPolygons
- E8: integration tests for scenario run/status endpoints

* fix(supply-chain): address PR #2910 review findings (P1 + P2 + P3)

- Wire setOnScenarioActivate + setOnDismissScenario in panel-layout.ts (todo #155)
- Rename shadow variable t→tmpl in SCENARIO_TEMPLATES.find (todo #152)
- Add statusResp.ok guard in scenario polling loop (todo #153)
- Replace status.result! non-null assertion with shape guard (todo #154)
- Add AbortController to prevent concurrent polling races (todo #162)
- Add polygonStrokeColor scenario branch (transparent) in GlobeMap (todo #156)
- Re-export SCENARIO_TEMPLATES via src/config/scenario-templates.ts (todo #157)
- Cache affectedIso2Set in DeckGLMap.setScenarioState (todo #158)
- Add scenario paths to PREMIUM_RPC_PATHS for auth injection (todo #160)
- Show template name in scenario banner instead of raw ID (todo #163)

* fix(supply-chain): address PR #2910 review findings

- Add auth headers to scenario fetch calls in SupplyChainPanel
- Reset button state on scenario dismiss
- Poll status immediately on first iteration (no 2s delay)
- Pre-compute scenario polygons in GlobeMap.setScenarioState
- Use scenarioId for DeckGL updateTriggers precision

* fix(supply-chain): wire panel instance to MapContainer, stop button click propagation

- Call setSupplyChainPanel() in panel-layout.ts so scenario banner renders
- Add stopPropagation() to Simulate Closure button to prevent card collapse
2026-04-10 21:31:26 +04:00

3.0 KiB

status, priority, issue_id, tags, dependencies
status priority issue_id tags dependencies
pending p2 159
code-review
performance
supply-chain
globe

flushPolygons Iterates All 250 GeoJSON Features on Every Flush — Pre-compute Scenario Polygons

Problem Statement

GlobeMap.flushPolygons() iterates over countriesGeoJsonData.features to build scenario GlobePolygon objects on every call. flushPolygons() is called whenever ANY polygon layer changes (CII update, conflict update, imagery toggle, storm cone render). With ~250 GeoJSON features, this is ~250 object allocations + ISO-2 Set lookups on every flush, even when the scenario state hasn't changed.

Findings

  • File: src/components/GlobeMap.ts
  • flushPolygons() constructs scenario polygons inline from this.scenarioState.affectedIso2s + countriesGeoJsonData on every call
  • Every other polygon layer (CII, conflict, imagery) pre-builds its polygon array before flushPolygons() and simply concatenates
  • setScenarioState() calls flushPolygons() but does not cache the pre-built polygon array
  • Identified by performance-oracle during PR #2910 review

Proposed Solutions

private scenarioPolygons: GlobePolygon[] = [];

public setScenarioState(state: ScenarioVisualState | null): void {
  this.scenarioState = state;
  if (!state?.affectedIso2s?.length || !this.countriesGeoJsonData) {
    this.scenarioPolygons = [];
  } else {
    const affected = new Set(state.affectedIso2s);
    this.scenarioPolygons = this.countriesGeoJsonData.features
      .filter(f => affected.has(f.properties?.['ISO3166-1-Alpha-2'] as string))
      .map(f => ({ ...buildGlobePolygon(f), _kind: 'scenario' as const }));
  }
  this.flushPolygons();
}

Then in flushPolygons():

const polys = [...this.ciiPolygons, ...this.conflictPolygons, ...this.scenarioPolygons, ...];
(this.globe as any).polygonsData(polys);

Pros: O(1) in flushPolygons() for scenario layer, consistent with existing pattern for other polygon types Cons: Slightly larger memory footprint (cached array) Effort: Small | Risk: Low

Option B: Keep inline, add early-exit guard

if (!this.scenarioState?.affectedIso2s?.length) { /* skip scenario loop */ }

Reduces cost to near-zero when no scenario active, but still O(n) when active. Effort: Trivial | Risk: None (acceptable trade-off)

Apply Option A if the inline loop is confirmed to run frequently (profile first). Option B is an acceptable minimal fix if profiling shows low impact.

Technical Details

  • Affected files: src/components/GlobeMap.ts
  • Add private scenarioPolygons: GlobePolygon[] = []
  • Move construction into setScenarioState()

Acceptance Criteria

  • flushPolygons() does not iterate GeoJSON features when scenario state is unchanged
  • setScenarioState() pre-builds scenarioPolygons
  • npm run typecheck passes

Work Log

  • 2026-04-10: Identified by performance-oracle during PR #2910 review

Resources

  • PR: #2910