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
This commit is contained in:
Lumen Stage1 2026-08-30 23:25:35 -05:00
commit c0bfd413ff
100 changed files with 9863 additions and 0 deletions

View file

@ -0,0 +1,141 @@
# 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.