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:
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user