- 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)
114 lines
4.5 KiB
Markdown
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*
|