# Refactor: O(N) existing-tile lookup via HashSet

**Task**: AZ-375_refactor_on_existing_tile_lookup
**Name**: HashSet-backed existing-tile membership test
**Description**: Replace the linear-scan tolerance check in `GoogleMapsDownloaderV2.DownloadTilesGridAsync` with a `HashSet<(int x, int y, int z)>` lookup keyed on integer tile coordinates.
**Complexity**: 2 points
**Dependencies**: AZ-371 (C18) — the remaining `0.0001` tolerance becomes a config value where it actually applies (geofence polygon check)
**Component**: Services.TileDownloader
**Tracker**: AZ-375
**Epic**: AZ-350

## Problem

`SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs:245-265` does `existingTiles.FirstOrDefault(t => Math.Abs(t.Latitude - tileCenter.Lat) < 0.0001 && Math.Abs(t.Longitude - tileCenter.Lon) < 0.0001 && t.ZoomLevel == zoomLevel)` per grid cell. That's a linear scan per cell — fine for ~16 tiles, quadratic for big regions. The tolerance is also redundant: tile coordinates at a fixed zoom are integers, so an exact tuple compare is correct.

## Outcome

- O(N) lookup via `HashSet<(int TileX, int TileY, int Zoom)>`.
- The magic `0.0001` tolerance at this site is gone (the other site — geofence polygon at `RouteService.cs:154` — is a real lat/lon tolerance and stays as config).
- Behavior identical for any input that already produces correct output.
- 37 unit + 5 smoke tests stay green.

## Scope

### Included
- Compute the integer `(TileX, TileY, Zoom)` for each row in `existingTiles` once, building the HashSet.
- Replace the per-cell `FirstOrDefault` with `set.Contains((tileX, tileY, zoom))`.
- Remove the unused `0.0001` literal at this site.

### Excluded
- Touching the geofence polygon tolerance check.
- Changing how `existingTiles` is fetched.

## Acceptance Criteria

**AC-1: HashSet membership replaces FirstOrDefault**
Given the post-refactor source
When `DownloadTilesGridAsync` is inspected
Then it builds a HashSet once and tests membership per cell.

**AC-2: Magic 0.0001 removed at this site**
Given the post-refactor `GoogleMapsDownloaderV2.cs`
When grepped for `0.0001`
Then matches are confined to non-tile-lookup contexts (or absent).

**AC-3: Tests stay green**
Given the post-refactor build
When `scripts/run-tests.sh --smoke` runs
Then all 37 unit + 5 smoke scenarios pass.

## Constraints

- For inputs that produce correct output today, behavior is identical.

## Risks & Mitigation

**Risk 1: existingTiles' lat/lon don't quantize cleanly back to integer tile coords**
- *Risk*: floating-point drift could mean two rows for "the same tile" produce different integer coords.
- *Mitigation*: convert via the same lat/lon→tile formula used to write them; if quantization concerns surface, add a regression test.

Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C22).
