Skip to content

docs(windows): spec the LTC and MTC schedule contract (#728 M3 slice 2) - #858

Merged
one-soft-engineer merged 1 commit into
devfrom
issues/728-m3-spec
Sep 16, 2026
Merged

one-soft-engineer merged 1 commit into
devfrom
issues/728-m3-spec

Conversation

@one-soft-engineer

Copy link
Copy Markdown
Collaborator

Summary

Adds the slice-2 spec for the Windows timecode contract (epic #728): LTCFrameStream, LTCSchedule and MTCSchedule. Spec only — no executable change in this PR.

Part of #728

What Changed

Slice 1 (#854) pinned what one LTC frame and one MTC message look like on the wire. This spec covers the layer above: how frames compose into a stream, how that stream is cut into playback buffers, and how a host-clock window selects the quarter-frames due in it.

Three findings, each verified numerically rather than argued:

  1. The parity bit is what makes LTC buffer joins seamless. Each frame emits 80 bit-boundary flips plus one mid-bit flip per 1 bit; the parity bit forces an even number of ones, so the flip count is even and endLevel always equals startLevel. All ten encode cases in golden/ltc-wire-v1.json confirm it. A side effect: LTCSchedule's doc comment justifies the inter-buffer polarity reset with "harmless, since an LTC reader keys on transitions, not absolute polarity" — that reasoning is wrong (a missing transition is a misread bit, not a polarity inversion), even though the conclusion holds for the stronger even-parity reason. The spec schedules a comment-only correction.

  2. Three fresh rounding divergences, plus an ordering hazard. framesPerBuffer(forTargetSeconds:) at 0.1 s @ 25 fps is 2.5 → Swift 3, C# Math.Round 2. Same at 0.5 s @ 25 fps (12.5 → 13 vs 12). MTCSchedule.timestamp diverges at a deliberately coarse clock. Separately, samplesPerBuffer rounds per frame then multiplies — at 44100 Hz / 24 fps that is 4 × 1838 = 7352, where the arithmetically-similar round(4 × 44100/24) gives 7350, i.e. a two-sample-per-buffer drift forever. Pinning only at 48 kHz (exact division) would miss it.

  3. MTCSchedule's 1 µs quantise before ceil is load-bearing. Simulated over the first 200 quarter-frames at ticksPerSecond = 1e9: without it a tiled window chain drops 74/200 messages at 24 fps and 66/200 at 30 fps, while 25 fps (exact 10 000 000-tick period) drops none. So the vectors must include a 1e9 @ 24 fps chain or the hazard is untested — and the C# spelling is Math.Ceiling with an inner AwayFromZero, not Math.Round.

The spec also records the consequence of (3) honestly: the quantise makes the half-open [from, until) window exact only to the tolerance. That never bites MTCOutput, which tiles, and the invariant that matters — each message exactly once across a chain — holds. Pinned as-is per the approved decision, with the reasoning left in the spec rather than discovered later.

Milestone label correction. Slice 1's spec and issue titles say "M2 slice 1". Epic #728's M2 is media; timecode I/O is M3 — hardware. The epic body is already corrected; this spec uses the right milestone and the filename carries the m3 prefix.

Affected Documentation

  • docs/superpowers/specs/2026-09-16-windows-m3-timecode-schedules.md — new. Goal, non-goals, four findings, five decisions, both golden-file contracts with full case matrices, work plan, implementation traps, and a mandatory eight-mutant mutation-testing list.

Review Checklist

  • All code examples are accurate and runnable
  • No broken links
  • Consistent formatting and terminology
  • Decision 1 — is "structure + a ±16-sample window around each join" the right pinning depth, given slice 1 already pins every sample of a single frame exhaustively?
  • Open question 1 — the synthetic ticksPerSecond = 50 clock is the only way found to make timestamp(forQuarterFrame:)'s rounding mode divergent, but it is unphysical. Keep the case, or accept that site is untested?
  • Open question 2 — Finding 4 is behaviour I would not have chosen from scratch, and pinning it makes it harder to change later.

OnlyCue verification (required)

Spec link: docs/superpowers/specs/2026-09-16-windows-m3-timecode-schedules.md
Closes: n/a — spec only; the implementation issue is filed after approval (also updates parent Epic #728 task list)

  • New tests added for every behavior (TDD: red→green committed) — n/a, documentation only; the spec itself defines the red-first sequence and the mutants that must kill
  • Gherkin scenarios from the issue mapped to UI tests where applicable — n/a, no user-visible behavior
  • Spec updated if behavior diverged from docs/ — this PR is the spec; docs/architecture.md is updated by the implementation PR
  • CI green

Slice 2 of the timecode contract: LTCFrameStream, LTCSchedule and
MTCSchedule. Pins composition (samples per frame/buffer, buffer
timecodes, the endLevel == startLevel invariant) plus PCM run-lengths in
a +/-16 sample window around each join, and MTCSchedule's host-clock
windowing including the 1us quantise before ceil.

Records three findings verified numerically rather than argued:

- the parity bit is what makes buffer joins seamless (even ones count =>
  even flip count => endLevel always equals startLevel), which also
  makes LTCSchedule's existing doc comment's justification wrong;
- three fresh half-away-from-zero rounding divergences plus one
  round-then-multiply ordering hazard in samplesPerBuffer;
- MTCSchedule's 1us quantise is load-bearing, not padding: at 1e9
  ticks/s a port without it drops 74/200 messages at 24 fps.

Also corrects the milestone label - this is M3 (hardware), not M2
(media), which slice 1's spec and issue titles got wrong.
@one-soft-engineer
one-soft-engineer merged commit 337dbd6 into dev Sep 16, 2026
2 checks passed
@one-soft-engineer
one-soft-engineer deleted the issues/728-m3-spec branch September 16, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants