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)
This commit is contained in:
113
.planning/phases/02-app-core-audio/02-04-SUMMARY.md
Normal file
113
.planning/phases/02-app-core-audio/02-04-SUMMARY.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
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*
|
||||
Reference in New Issue
Block a user