feat: task 2.3 sprite preload (racing categories)
- src/map/assets/icons/: 9 placeholder SVGs (background plate, direction arrow, 7 categories: rally-car / quad / ssv / motorcycle / runner / hiker / default). Hand-authored simple silhouettes; replaced in Phase 3.8 with the branded set. - src/map/core/categories.ts: SpriteCategory/SpriteColor types, mapCategoryToSprite() normaliser, inferColor() helper. - src/map/core/sprite-preload.ts: idempotent preloadSprites() with memoised promise, whenSpritesReady() alias, getSpriteRegistry() read-only access, installSprites(map) for the mapReady flow. Composition pipeline draws the plate + composites a tinted icon centred on it (category sprites) or tints the arrow alone (direction sprites). Tint via canvas globalCompositeOperation: 'source-in'. - src/main.tsx: void preloadSprites() fired at boot; the promise is memoised so mapReady flow awaits the same instance. - src/map/core/map-view.tsx: onStyleData() awaits whenSpritesReady() AND _map.loaded() before installing sprites and flipping mapReady true. Sprites reinstall on every style swap. Registry: 7 categories x 4 colours + 4 direction-only entries = 32 total. ~160KB in memory. Deviations: 1. Direction sprites have no plate (it's a separate symbol layer in 2.5 overlaid on the device sprite; double-plate would look wrong). 2. Hardcoded the design-system palette (#2E8C4A / #E8412B / #0E0E0C / #2563C8) directly. When 3.8 lands, these rebind to TRM tokens via CSS variables.
This commit is contained in:
@@ -219,6 +219,7 @@ On first visit, default to `esri-satellite` — the most universally useful for
|
||||
2. Spec sketched `BasemapDescriptor.style` as `StyleSpecification | string`. Implemented as `buildStyle: (cfg) => StyleSpecification` — a function. Reason: the Google variant's `style` depends on `cfg.googleMapsKey`, so it has to be computed at switch time. Uniform function shape across all entries keeps the type clean.
|
||||
|
||||
**Smoke check (local `pnpm dev`):**
|
||||
|
||||
- `/monitor` shows three buttons top-right: Satellite / Topo / Street. Clicking each switches the basemap.
|
||||
- Selected button highlights via `bg-accent`.
|
||||
- Reloading the page restores the previously selected basemap.
|
||||
|
||||
@@ -146,4 +146,24 @@ For 2.3, just include it in the registry and it'll be there when 2.5 needs it.
|
||||
|
||||
## Done
|
||||
|
||||
(Filled in when the task lands.)
|
||||
- **`src/map/assets/icons/`** — 9 SVGs: `background.svg` (rounded square plate), `direction.svg` (arrow), and one per category (`rally-car`, `quad`, `ssv`, `motorcycle`, `runner`, `hiker`, `default`). Plus a `README.md` documenting the composite-tint pipeline and flagging the placeholder status (3.8 replaces with branded set).
|
||||
- **`src/map/core/categories.ts`** — `SpriteCategory` / `SpriteColor` / `SpriteKey` types, `SPRITE_CATEGORIES` / `SPRITE_COLORS` arrays, `mapCategoryToSprite()` normaliser (handles `car / pickup / truck` → `rally-car`, `atv / four-wheeler` → `quad`, etc.), `inferColor()` helper used by 2.5.
|
||||
- **`src/map/core/sprite-preload.ts`** — `preloadSprites()` (idempotent, memoised promise), `whenSpritesReady()` (alias), `getSpriteRegistry()` (read-only access), `installSprites(map)` (called from mapReady flow). `composeCategorySprite()` draws the background plate then composites a tinted icon centred on it; `composeDirectionSprite()` tints the arrow only (no plate). `tintImage()` uses canvas `globalCompositeOperation: 'source-in'` + `fillRect` — the SVG's alpha mask becomes the tint mask.
|
||||
- **`src/main.tsx`** — `void preloadSprites()` fired once at boot. The promise is memoised inside `preloadSprites()` so the mapReady flow's `whenSpritesReady()` call awaits the same promise.
|
||||
- **`src/map/core/map-view.tsx`** — `onStyleData()` flow updated to await `whenSpritesReady()` AND `_map.loaded()` before flipping `mapReady` true and installing sprites. Sprites get reinstalled after every style swap.
|
||||
|
||||
**Registry size:** 7 categories × 4 colours = 28 entries, plus 4 direction-only entries (one per colour, no plate). 32 total. Each at ~5KB ImageData = ~160KB in memory.
|
||||
|
||||
**Deviations from spec:**
|
||||
|
||||
1. Spec sketched the direction sprite as part of the same composition (background + tinted icon). Implemented as two separate sprite types: category sprites have the plate, direction sprites are the arrow alone (no plate). Reason: the direction sprite is rendered as a *separate* symbol layer in 2.5, overlaid on top of the device sprite — drawing a plate under the arrow would create a double-plate visual. The spec's example expression `'icon-image': '{category}-{color}'` for one symbol layer + `'icon-image': 'direction-{color}'` for the direction layer is what 2.5 will actually consume.
|
||||
2. Spec showed sample colours as `success: 'green'`. Used the design system's actual semantic palette (`#2E8C4A` green, `#E8412B` race-flag red, `#0E0E0C` ink, `#2563C8` info blue) directly. When 3.8 lands, these get rebound to TRM design tokens via CSS variables.
|
||||
|
||||
**Smoke check (local `pnpm dev`):**
|
||||
- App boots; `getSpriteRegistry().size` returns `32` after the page settles.
|
||||
- `/monitor` map renders; switching basemaps doesn't break sprites (visible via the mapReady flow's "preload then install" sequence in dev tools console).
|
||||
- No "Image with id X is missing" warnings.
|
||||
|
||||
**Bundle:** SVGs are inlined as data URLs by Vite (each is ~200-400 bytes). The sprite-preload module itself is small (~3KB). The map chunk gains ~5KB.
|
||||
|
||||
Landed in `PENDING_SHA`.
|
||||
|
||||
@@ -50,7 +50,7 @@ When Phase 2 is done:
|
||||
| --- | ------------------------------------------------------------------------------------------ | ------ |
|
||||
| 2.1 | [MapView singleton + mapReady gate](./01-mapview-singleton.md) | 🟩 |
|
||||
| 2.2 | [Tile-source switcher](./02-tile-source-switcher.md) | 🟩 |
|
||||
| 2.3 | [Sprite preload (racing categories)](./03-sprite-preload.md) | ⬜ |
|
||||
| 2.3 | [Sprite preload (racing categories)](./03-sprite-preload.md) | 🟩 |
|
||||
| 2.4 | [WS client + rAF coalescer + Zustand position store](./04-ws-client-and-position-store.md) | ⬜ |
|
||||
| 2.5 | [MapPositions (clustered + selected sources)](./05-map-positions.md) | ⬜ |
|
||||
| 2.6 | [MapTrails (bounded ring buffer, polyline rendering)](./06-map-trails.md) | ⬜ |
|
||||
|
||||
Reference in New Issue
Block a user