Skip to content

docs(windows): spec the LTC decoder contract (#728 M3 slice 3) - #861

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

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

Conversation

@one-soft-engineer

@one-soft-engineer one-soft-engineer commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Spec for #728 M3 slice 3 — pinning LTCDecoder as golden/ltc-decode-v1.json with a LtcDecoder mirror in OnlyCue.Core. Docs only; no code.

Slices 1 (#854) and 2 (#859) pinned what OnlyCue emits — the wire format, then the timing plan. This slice pins what it believes when it reads, which is the harder half: a decoder that is subtly wrong still returns plausible timecode on a clean signal and only diverges at the edges.

Motivation

The decoder is the largest pure algorithm left unpinned in the timecode subsystem (178 lines of comparator → histogram → demodulator → framing), and every stage has a threshold, a rounding, or an advance-by-N that a reasonable C# port would get nearly right. Once this lands, the whole LTC path is pinned in both directions on both platforms: a Windows build can stripe a file and read one back and be provably byte-compatible with macOS.

What Changed

  • docs/superpowers/specs/2026-09-16-windows-m3-ltc-decoder.md — the spec.

Scope decisions worth reviewing

Nine named hazards, each of which gets at least one case whose expectation would change if the port got it wrong. The two most likely to be ported "nearly right":

  • extractFrames advances 80 on a sync match, not on a valid frame. A frame with a good sync word but bad parity is not merely dropped — it consumes its 80 bits and the search resumes after it. A port that moves end += 80 inside the isWellFormed check keeps searching bit-by-bit through the corrupted frame and can lock onto a spurious window. Invisible on clean signals, so it gets a dedicated case.
  • The first comparator latch is not recorded as a transition. state starts at 0 and the first latch sets it without appending an index. A port that appends it shifts every startSample and adds a leading interval to the histogram.

Plus the float-width ones: rms accumulates in Double and narrows to Float exactly once, and the threshold multiply happens in Float.

Noise cases are deliberately excluded. Reproducing added noise / DC drift cross-platform needs a seeded PRNG whose output becomes part of the contract — that pins the PRNG, not the decoder. Structural corruption (flipped parity bit, broken sync word, out-of-range BCD, truncation, silence, amplitude below the floor) is deterministic, expressible in the vector, and is where ports actually break. One exception is pinned as a non-decode: a DC-offset buffer must fail, because the comparator is symmetric about zero and has no DC blocker — pinning the failure stops a port from "helpfully" adding one.

Signals are recipes, not sample dumps. A case names generator parameters plus an ordered list of mutations; both sides build the identical float[]. Keeps the file readable and still pins the signal.

Five ops instead of one. decode / transitions / halfBit / framesPerSecond / bits. A single decode op would make every hazard fail the same way and the mutation step could not tell them apart.

Two things the spec flags about itself

  • InternalsVisibleTo is not configured today. The spec initially claimed it was; I checked OnlyCue.Core.csproj and it has only TargetFramework / ImplicitUsings / Nullable, with no reference to the attribute anywhere in the tree. Adding it is therefore part of the slice and is called out rather than slipped in — the intermediate seams should be internal (mirroring Swift's private) rather than public, because pinning an intermediate is not a reason to widen the shipping surface.
  • The mutation predictions are labelled hypotheses, not requirements. Slice 2 falsified two of its own, and the spec now carries that rule explicitly: if a mutant survives, prove equivalence over the reachable domain empirically or fix the contract — never assume it is harmless, never delete the finding. Mutant 6 (banker's rounding for framesPerSecond) gets a further instruction: if no case can make it go red, the honest conclusion may be that the matrix is incomplete rather than that the mutant is harmless.

How to Verify

  • Docs-only — no build or test surface. swiftlint lint --strict unaffected.
  • Reviewer agrees the hazard list is complete and the noise exclusion is the right call.

Out of Scope

No implementation. PLL jitter tracking, half-speed and reverse playback are out (the type does not do them; pinning them would pin fiction). LTCAudioReader is AVFoundation-facing and gets its own Windows front end. LTCBiphaseEncoder stays outside the contract for the reason recorded in slice 1.


OnlyCue verification (required — doc)

Spec link: docs/superpowers/specs/2026-09-16-windows-m3-ltc-decoder.md
Closes: — (spec only; the implementation issue is filed once this is approved, and updates epic #728)

  • Documentation reflects the code as merged (describes work not yet started, and says so)
  • Links / anchors valid
  • No behavioral change
  • CI green

@one-soft-engineer
one-soft-engineer merged commit defb0a7 into dev Sep 16, 2026
2 checks passed
@one-soft-engineer
one-soft-engineer deleted the issues/728-m3-slice3-spec branch September 16, 2026 15:04
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