--- phase: v0.2.0-polish plan: v0.2.0 subsystem: chart-realtime tags: [kmp, compose, canvas, buffer, lod, tiered-buffer, lttb, min-max, axis, material3] requires: - phase: v0.1.0-mvp provides: CircularBuffer, LodDecimator, RealtimeChart composable, basic AxisRenderer provides: - TieredBuffer (3-tier 1h ring buffer, MIN_MAX binning, zero-alloc) - LodMode enum (MIN_MAX, LTTB) - LTTB render-time decimation - AxisLabelMode (INSIDE/BESIDE/HIDDEN) with chartLeft/chartBottom insets - T0-relative X-axis timestamps with mm:ss/hh:mm:ss formatting - Material3 dynamic theming (light/dark, wallpaper-based) - Camera cutout / edge-to-edge support - Sensor domain layer (Clean Architecture) - Koin DI wiring - Package: dev.dtrentin.chart - Git repository on ssh://3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git affects: [future iOS target, future persistence layer] tech-stack: added: [koin-4.0.0, kotlinx-coroutines-android, androidx-lifecycle-runtime-compose] patterns: [TieredBuffer hot-path write, MIN_MAX composable decimation, LodDecimator as class with pre-alloc scratch] key-files: created: - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/buffer/TieredBuffer.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/model/LodMode.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/model/AxisLabelMode.kt - sensor-domain/src/commonMain/kotlin/dev/dtrentin/sensor/domain/ modified: - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/buffer/LodDecimator.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/buffer/CircularBuffer.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/AxisRenderer.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/SignalRenderer.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/RealtimeChart.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/RealtimeChartState.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/model/ChartConfig.kt - chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/model/ChartTheme.kt key-decisions: - "TieredBuffer over flat ring: O(signals × constant) memory, scales to 20+ signals" - "MIN_MAX binning for tiers: composable operation, zero visual discontinuity at boundaries" - "MEAN removed: introduces values never in raw signal, disinformation for sensor data" - "LodDecimator as class: pre-alloc scratch arrays, zero GC at render time" - "Sensor rate capped at 200Hz: thermal/battery optimization" - "Package dev.dtrentin: personal project, not company" patterns-established: - "MIN_MAX composability: MIN(bin_mins)=global_MIN across tier boundaries" - "TieredBuffer snapshot always time-sorted: tier2→tier1→tier0 with tier0Start=max(windowStart,tier0Boundary)" - "Render arrays pre-alloc in RealtimeChart via remember { }, zero alloc per frame" duration: ~8h (multi-session) started: 2026-05-20T00:00:00Z completed: 2026-05-21T00:00:00Z --- # v0.2.0 Plan Summary **chart-realtime KMP library rebuilt from scratch: TieredBuffer (1h/20-signal), LTTB+MIN_MAX LoD, axis labels with INSIDE/BESIDE/HIDDEN modes, Material3 dynamic theming, Clean Architecture sensor domain — shipped as v0.5.0-SNAPSHOT.** ## Performance | Metric | Value | |--------|-------| | Duration | ~8h multi-session | | Started | 2026-05-20 | | Completed | 2026-05-21 | | Versions shipped | 0.2.0→0.3.0→0.4.0→0.4.1→0.5.0-SNAPSHOT | | Git commits | 3 (initial + 2 incremental) | | Files modified | 20+ | ## Acceptance Criteria Results | Criterion | Status | Notes | |-----------|--------|-------| | Axis text labels visible | Pass | AxisRenderer TextMeasurer, INSIDE/BESIDE/HIDDEN modes | | Dynamic X-window from UI | Pass | Slider 2–3600s, StateFlow, MAX_BUFFER_SECONDS=3600 | | Material3 theme compliance | Pass | rememberMaterialChartTheme(), dynamic color API 31+ | | User-decided signal colors | Pass | SignalConfig.color, ChartTheme separate | | Camera cutout / edge-to-edge | Pass | enableEdgeToEdge() + Scaffold innerPadding | | 1h data support without OOM | Pass | TieredBuffer ~18MB for 6 signals | | No tier boundary visual artifact | Pass | MIN_MAX composable + tier0 lower-bound fix | | LodDecimator zero-alloc | Pass | Class with pre-alloc scratch arrays | | Negative X-axis values | Pass | formatTimeSec handles negative elapsed time | | Sensor rate cap 200Hz | Pass | SensorRepositoryImpl 5_000μs | | Package renamed dev.dtrentin | Pass | eu.henesis → dev.dtrentin everywhere | | Remote git repository | Pass | ssh://3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git | ## Accomplishments - **TieredBuffer**: 3-tier ring buffer (tier0=5min@200Hz, tier1=10min@10Hz MIN_MAX, tier2=45min@1Hz MIN_MAX). ~18MB for 6 signals vs 259MB naive flat at 1000Hz assumption - **MIN_MAX composability**: tier boundary artifacts eliminated. MIN(tier_mins)=global_MIN mathematically proven. MEAN removed as inherently dishonest for raw sensor data - **LTTB algorithm**: O(n) Largest-Triangle-Three-Buckets in LodDecimator. Preserves peaks, smooth trend line, better than mean without signal distortion - **Axis overhaul**: INSIDE/BESIDE/HIDDEN modes, chartLeft/chartBottom insets, T0-relative timestamps (mm:ss/hh:mm:ss), scientific notation for |y|<0.01 or >1e5, negative values, grid toggle ## Deviations from v0.2.0 Plan | Type | Count | Description | |------|-------|-------------| | Scope additions | 15+ | All features beyond Material3/axis/xWindow/scratch | | Auto-fixed bugs | 8 | Tier boundary, LTTB samplePtr, X label sticky, Y label clipping, LoD path unravel, etc. | | Deferred | 1 | iOS expect/actual — still commonMain only | **Key deviations:** - Phase 4 (scratch optimization): addressed differently — LodDecimator converted from `object` to `class` with pre-allocated instance fields, not param-passing as planned - CircularBuffer replaced by TieredBuffer at storage level: larger scope than originally planned - MEAN LodMode added then removed in same milestone after analysis showed it was harmful ## Key Decisions | Decision | Rationale | Impact | |----------|-----------|--------| | TieredBuffer over flat ring | O(N×constant) memory, scales to 20+ signals at any rate | Core buffer architecture | | MIN_MAX tier binning | Composable: MIN(mins)=global_MIN, no artifacts | Eliminates tier discontinuity | | MEAN removed from LodMode | Introduces synthetic values never in raw signal | Cleaner API, honest visualization | | Sensor cap 200Hz not FASTEST | Thermal/battery optimization, 200Hz exceeds visual need | Reduces CPU interrupts 4-10× | | dev.dtrentin namespace | Personal project, not company IP | Package identity | ## Bugs Fixed | Bug | Root Cause | Fix | |-----|-----------|-----| | TieredBuffer tier artifact | tier0 snapshot lacked `ts >= tier0BoundaryMs` filter → double-inclusion → col-sort broken | Added `tier0Start = maxOf(windowStart, tier0BoundaryMs)` | | LoD path unravel | LodDecimator mapped out-of-window samples (tRelMs<0) to x=0 | Added in-window filter before binning | | X label sticky at axis | `coerceAtLeast(chartLeft)` clamped all early labels to axis line | Replaced with `if (labelX >= chartLeft)` skip | | Y labels clipped bottom | Used raw `14f` px instead of density-aware sp | `with(this) { 12.sp.toPx() }` | | Signal line outside chart | No clipRect on drawPath | Added `clipRect(left=chartLeft…)` | | Negative X shows "0s" | `coerceAtLeast(0L)` | `abs()` + `-` prefix for negative | | OOM at MAX_X_WINDOW=3600 | MAX_SAMPLE_RATE=1000Hz assumed, 259MB pre-alloc | Reduced to 200Hz, TieredBuffer replaces flat ring | ## Next Phase Readiness **Ready:** - Library stable at v0.5.0-SNAPSHOT, published to mavenLocal - Remote git: `ssh://3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git` - AndroidChartsApp consuming library, builds clean - Clean Architecture sensor domain in place **Potential next work:** - Dirty flag render (skip frame if no new data) — thermal/battery improvement - targetFps=30 default — thermal improvement - iOS expect/actual for currentTimeMs (currently Android only) - Publish to Maven Central or GitHub Packages - KDoc on public API surface - README with integration example **Deferred:** - iOS support (expect/actual beyond currentTimeMs) - Adaptive sensor rate based on xWindowSeconds --- *Phase: v0.2.0-polish, completed 2026-05-21*