Lumen/SPIKE-03-MAP-REPRESENTATION.md
Lumen Stage1 c0bfd413ff Stage 1: project foundation (strict TS, lint boundaries B-1..B-7, directory structure, CI, boundary tests)
- dedicated git repo at /home/avi/Projects/Lumen (main)
- TypeScript strict (target ES2022, bundler, exactOptionalPropertyTypes, noUncheckedIndexedAccess)
- ESLint 9 + typescript-eslint strictTypeChecked + eslint-plugin-boundaries for B-1..B-7, no-restricted-globals/syntax for B-1/B-7
- Prettier 3.5
- Structure per IMPLEMENTATION-CONTRACT.md §4 (src/platform/idb|cache|sw, storage, data, sync/{transport,verifier}, domain/{emergency,schedule,map,festival,readiness,clock,favorites}, ui/{components,views,router,render}, app, emergency-baseline, assets, public, content, pipeline, tests, scripts)
- CI: .github/workflows/ci.yml (typecheck + lint + format + test)
- Boundary tests: tests/unit/boundaries.test.ts (4 tests) + scripts/check-boundaries.ts
- No feature code, no PWA/IDB/sync/mesh/accounts per contract Stage 1
2026-08-30 23:25:35 -05:00

141 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPIKE-03 — Map Representation
- **Phase:** Architecture Validation
- **Date:** 2026-08-30
- **Decision under test:** ADR-008 (raster WebP base ≤2 levels + DOM POI
overlay + CSS-transform pan/zoom + Facilities list; no online tiles; no GPS).
- **Method:** structured evaluation against all candidate representations;
decode-memory analysis; headless synthetic benchmark
(`experiments/exp3-map-bench.html`) with strict interpretation limits.
## 1. Candidates
| | S1 Single SVG map | S2 Raster base + DOM overlay (proposed) | S3 Canvas | S4 Pre-cached raster tile pyramid | S5 Hybrid vector-POI over raster (variant of S2) |
|---|---|---|---|---|---|
| Offline completeness | Yes | Yes | Yes | Yes | Yes |
| Organizer workflow | Needs vector art skills; illustrated maps are raster | Art as-is (illustrated PNG/WebP) | Same as S2 | Same as S2 | Same as S2 |
| Low-end pan/zoom | Degrades with path count | GPU transform of 1 image — cheap, constant | Full redraw per frame | Tile management code | Same as S2 |
| Accessibility | Awkward at scale | POIs are real buttons; list view first-class | Needs parallel text tree | Same as S3 | Same as S2 |
| Zoom quality | Infinite | Bounded by 2 levels (venue-scale adequate) | Same as S2 | Good | Same as S2 |
| File size | Art-dependent | WebP excellent for illustrated art | Same | + overhead per tile | Same |
| POI interaction | DOM/SVG events | DOM buttons (48 px targets trivial) | Manual hit-testing | Same as S3 | Same as S2 |
| Search / dynamic filtering | Possible | Trivial (data-driven markers, class toggles) | Manual | Manual | Trivial |
| Dark mode | Styleable | Needs dimming strategy (see §4) | Manual | Manual | Same as S2 |
| Maintainability / update flow | Re-export vector | Replace image + POI sheet → new package | Same as S2 | Same | Same |
| Code surface | Medium | Low | Higher | Highest | Low |
## 2. Benchmark evidence (EXP-3) and its limits
Headless desktop run (see `experiments/README.md` for caveats): all three
mechanically viable variants (S2, S1, S3) measured **≈ 0 ms synchronous JS per
animation frame** at 100 and 300 POIs.
**Interpretation (deliberately conservative):** desktop JS cost does not
discriminate the candidates. The costs that matter on low-end mobile — GPU
compositing of transformed layers, texture upload of large images, decode
latency, RAM pressure — are invisible to this benchmark. Therefore EXP-3
confirms only: (a) no candidate has a disqualifying JS-side cost; (b) the
DOM-overlay and SVG transform mechanics work as designed. The final choice
rests on memory, accessibility, workflow, and robustness reasoning below, with
a **mandatory device protocol** in §7.
## 3. Memory analysis (the decisive axis on mobile)
Decoded image cost ≈ `width × height × 4` bytes (RGBA):
| Base image | Encoded (WebP, illustrated art) | Decoded in RAM |
|---|---|---|
| 1600×1200 (overview cap) | ~0.3–0.7 MB | ~7.3 MB |
| 2048×1536 | ~0.5–1.2 MB | ~12 MB |
| 3072×2304 (detail cap) | ~1.5–3.5 MB | ~27 MB |
| 4096×3072 (previous cap) | ~2.5–6 MB | ~48 MB |
| 4096×4096 | — | ~64 MB |
Findings:
- **F-1 (change):** ARCH §15.3's "≤ 4096 px per side" cap was chosen for GPU
texture limits but is **insufficient as a memory constraint**. On a 3–4 GB
low-end Android, holding a 48–64 MB decoded bitmap alongside the app, list
caches, and browser overhead is a jetsam/OOM risk. **New caps: overview ≤
1600 px longest edge; detail ≤ 3072 px longest edge; total decoded map
memory ≤ ~35 MB; at most one detail-level image resident** (overview is
swapped out or downscaled when detail is shown). Some older GPUs cap
textures at 2048 px; Chromium tiles oversized textures internally (works, at
a cost) — the 3072 cap plus an overview-only fallback mode covers this.
- **F-2:** WebP encoded sizes at these dimensions fit the 28 MB asset budget
with margin, including per-POI icons.
- **F-3:** IDs/object URLs: create object URLs lazily per visible level;
revoke on level switch to free decode memory.
## 4. Criteria-by-criteria results for the chosen representation
- **Initial load:** overview WebP (~0.5 MB) + map.json renders the usable map
immediately; detail level lazy-loads on zoom-in. CONFIRMED by design.
- **Zoom/pan:** single-container CSS transform; markers counter-scaled.
Mechanics CONFIRMED viable by EXP-3; fps on device UNVERIFIED (§7).
- **Retina/high-density:** base authored at ~2× intended display size;
downscale-on-fit keeps quality. INFERRED.
- **Accessibility / screen readers:** POIs are real DOM buttons with labels;
Facilities list is a first-class, non-visual equivalent (not a fallback).
Base image carries descriptive `alt`. CONFIRMED by design.
- **POI interaction:** tap → detail sheet; 48 px targets. CONFIRMED by design.
- **Search:** shared search component indexes POI names/categories; results
highlight markers + list entries. CONFIRMED by design.
- **Dynamic filtering:** category chips toggle marker visibility (data-driven,
class/attribute toggles; no re-layout of the base). CONFIRMED by design.
- **Dark mode (F-4, change):** festival art is typically day-illustrated.
Default: CSS `filter: brightness(.72) saturate(.85)` on the base image in
dark theme + full-brightness markers/labels (cheap, GPU-composited).
Optional: organizer-supplied night-variant asset (schema hook: asset role
`map-base/overview-night`). No white flash, no re-download required.
- **Organizer workflow:** illustrated art delivered as raster + POI coordinate
capture step (tap-tool over the image in pipeline tooling, or surveyed
normalized coordinates). Map update = new package version; no app change.
INFERRED — needs confirmation with real art (OQ-6, AQ-19).
- **Offline use:** all bytes local; no tile service, no GPS. CONFIRMED.
## 5. When the decision would flip
- Organizers supply **vector** art (SVG) of good quality → reconsider S1/S5
(crisp zoom, smaller files) — SPIKE verdict remains valid either way because
POI overlay + list view are representation-agnostic.
- Real art exceeds 3072 px requirement for legibility → tile-split the detail
level (2×2), still local; bounded extra complexity (DD-9).
- Device protocol (§7) shows transform jank on the low-end Android target →
fallback: overview-only mode + reduced marker count; canvas remains the
escape hatch but is not preemptively adopted.
## 6. Cross-checks with other decisions
- ADR-004: assets as Blobs in the slot DB — consistent; object-URL lifecycle
managed by MapService (F-3).
- SPIKE-05: map missing ⇒ PARTIAL with list still available if POI data
present — consistent.
- ADR-012: POI optional `lat/lng` hook preserved; no GPS in V1 — consistent.
## 7. Mandatory physical-device protocol (before production)
On the low-end Android target and an iPhone:
1. Pan/zoom sustained 10 s at 60 Hz intent with 200 POIs → observe fps/frame
drops (target ≥ 30 fps).
2. Detail-level first zoom-in: decode latency and memory peak
(`performance.memory` on Chrome; Instruments on iOS).
3. Overview→detail→overview cycling: no leaked object URLs/memory growth.
4. Dark-mode filter visual quality + performance cost.
5. Screen-reader pass over POI markers and Facilities list (TalkBack,
VoiceOver).
6. Older-GPU device (2048 texture cap) with a 3072 px detail image: renders
correctly via Chromium tiling; measure cost; exercise overview-only
fallback.
## 8. Verdict
**ACCEPT WITH CHANGES.**
Representation stands: **raster WebP base (≤2 levels) + DOM POI overlay +
CSS-transform pan/zoom + first-class Facilities list**, no tiles, no GPS.
Changes (to ADR-008): image dimension/memory caps tightened (F-1), object-URL
lifecycle rule (F-3), dark-mode treatment (F-4), night-variant asset hook.
Condition: device protocol §7 must pass before the map is declared
production-ready; fallbacks are predefined.