- 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
7.8 KiB
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 rolemap-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/lnghook preserved; no GPS in V1 — consistent.
7. Mandatory physical-device protocol (before production)
On the low-end Android target and an iPhone:
- Pan/zoom sustained 10 s at 60 Hz intent with 200 POIs → observe fps/frame drops (target ≥ 30 fps).
- Detail-level first zoom-in: decode latency and memory peak
(
performance.memoryon Chrome; Instruments on iOS). - Overview→detail→overview cycling: no leaked object URLs/memory growth.
- Dark-mode filter visual quality + performance cost.
- Screen-reader pass over POI markers and Facilities list (TalkBack, VoiceOver).
- 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.