- 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
141 lines
7.8 KiB
Markdown
141 lines
7.8 KiB
Markdown
# 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.
|