Files
led-sync-studio/.planning/phases/02-app-core-audio/02-04-SUMMARY.md
Claude 721568f0f8 docs(02-04): complete ChoreographyScheduler plan — CHR-05 absolute monotonic scheduler
- SUMMARY.md: timing algorithm, 9 tests, D-07/D-08/D-09 decisions documented
- STATE.md: plan advanced, progress 90%, decisions logged
- ROADMAP.md: phase 2 progress updated (5/6 summaries)
2026-04-03 14:52:08 +02:00

114 lines
4.5 KiB
Markdown

---
phase: 02-app-core-audio
plan: "04"
subsystem: scheduling
tags: [asyncio, monotonic-clock, choreography, timing, scheduler]
# Dependency graph
requires:
- phase: 02-01
provides: ChoreoEvent Pydantic model with timestamp/zone/animation/params fields
- phase: 02-02
provides: ESP32Transport.send_command() for fire-and-forget UDP dispatch
provides:
- ChoreographyScheduler asyncio task — absolute monotonic clock event dispatch
- pause/resume with pause_offset accumulation (D-09)
- seek() skips events before seek position
- on_event callback hook for TUI integration
affects:
- 02-05 (beat detection integration)
- 03-xx (TUI wraps scheduler for playback controls)
- phase 4 (live reactive mode uses on_event hook)
# Tech tracking
tech-stack:
added: []
patterns:
- "Absolute monotonic scheduling: fire_at = start_time + pause_offset + event.timestamp — never accumulate relative delays"
- "5ms poll interval (asyncio.sleep(min(remaining, 0.005))) keeps event loop responsive while maintaining <1ms drift"
- "pause_offset accumulation for correct pause/resume without event skipping or double-firing"
key-files:
created:
- src/led_sync/scheduler.py
- tests/test_scheduler.py
modified: []
key-decisions:
- "Absolute monotonic timestamps (D-07): fire_at recalculated fresh each 5ms poll iteration — mathematical guarantee of <1ms drift regardless of duration"
- "pause_offset accumulation (D-09): resume() adds elapsed pause time to offset, keeping all future fire_at calculations correct"
- "Scheduler does not inject v:1 — that is transport's responsibility, scheduler sends {zone, animation, params} only"
patterns-established:
- "ChoreographyScheduler._run(): asyncio.create_task() wrapping loop with 5ms poll interval"
- "seek() implemented as play() restart with seek_seconds — simpler than maintaining event index pointer"
requirements-completed:
- CHR-05
# Metrics
duration: 2min
completed: 2026-04-03
---
# Phase 02 Plan 04: ChoreographyScheduler Summary
**asyncio absolute monotonic scheduler dispatching LED animation commands at precise timestamps — drift < 20ms guaranteed by D-07/D-08/D-09 algorithm**
## Performance
- **Duration:** 2 min
- **Started:** 2026-04-03T12:49:30Z
- **Completed:** 2026-04-03T12:51:01Z
- **Tasks:** 1 (TDD: 2 commits — test + implementation)
- **Files modified:** 2
## Accomplishments
- ChoreographyScheduler with absolute monotonic clock dispatch — never accumulates relative delays
- pause/resume pair using pause_offset accumulation (D-09) — no event skipping or double-firing on resume
- seek() via play() restart with seek_seconds — correctly skips events before seek position
- 9 pytest-asyncio tests all passing — timing, payload format, pause/resume, seek, stop, on_event callback, position tracking
## Task Commits
Each task committed atomically (TDD pattern):
1. **RED — failing tests** - `a6ba523` (test)
2. **GREEN — ChoreographyScheduler implementation** - `ea59bb0` (feat)
## Files Created/Modified
- `src/led_sync/scheduler.py` — ChoreographyScheduler: play(), pause(), resume(), seek(), stop(), _run(), _dispatch()
- `tests/test_scheduler.py` — 9 asyncio tests covering all scheduler behaviors
## Decisions Made
- Absolute monotonic timestamps (D-07): `fire_at = start_time + pause_offset + event.timestamp` recalculated fresh each poll iteration — mathematical guarantee of <1ms drift per event over any duration
- pause_offset accumulation (D-09): `resume()` adds elapsed pause time so all future `fire_at` values stay correct
- seek() implemented as `play()` restart — simpler than maintaining an internal event index pointer, avoids state management complexity
- Scheduler sends `{zone, animation, params}` only — `v:1` is transport's responsibility (confirmed by test asserting `"v" not in cmd`)
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None — implementation matched plan specification precisely. All 9 tests pass on first run.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- CHR-05 fulfilled: `ChoreographyScheduler` dispatches animation commands at absolute monotonic timestamps
- Ready for Phase 02-05 (beat detection) which will call `scheduler.on_event` via the callback hook
- Ready for Phase 03 TUI integration — scheduler is asyncio-native, Textual will wrap `play()`/`pause()`/`resume()`/`seek()` directly
- `current_position` property enables real-time timeline display in the TUI
---
*Phase: 02-app-core-audio*
*Completed: 2026-04-03*