Skip to content
Merged
8 changes: 6 additions & 2 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,13 @@ QUIC studies there on that rule.
- [Capture by default](/quest/m1/capture-default.md) - moq-video and moq-audio build `capture` by default, so pre-merge checks test it and the capture gate goes away
- [Ship capture and playback](/quest/m1/cli-packaging.md) - a released moq binary can capture and play, which no distribution currently enables
- [Go and Dart doc samples](/quest/m1/doc-samples-go-dart.md) - Go and Dart doc samples compile against their wrappers
- [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md) - no Rust or binding publish API fills in a timestamp; an untimed payload goes out untimed, and moq-mux data producers take broadcast-clock timestamps
- [Mux data timestamp](/quest/m1/mux-data-timestamp.md) - moq-mux JSON and binary producers take a broadcast-clock timestamp, with no ahead-of-now refusal
- [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md) - no Rust or binding publish API fills in a timestamp; an untimed payload goes out untimed
- [JS publishing never invents a timestamp](/quest/m1/js-publish-timestamp.md) - the same in @moq/json, @moq/binary, and @moq/net
- [Plan: untimed objects](/quest/m1/plan-untimed-objects.md) - map faithful timestamp absence, publisher to consumer, into implementation quests
- [Untimed model](/quest/m1/untimed-model.md) - moq-net frames and datagrams carry an absent timestamp to every subscriber; no receiver fills in arrival time
- [JS untimed model](/quest/m1/js-untimed-model.md) - the same in @moq/net
- [Untimed lite-07](/quest/m1/lite-untimed.md) - lite-07 encodes an absent timestamp in both languages; lite-05/06 write send time
- [Plan: untimed verbatim PES](/quest/m1/plan-ts-pes-untimed.md) - decide how a verbatim TS track carries a PES that has no PTS, then write the implementation quest
- [Data consumer timestamps](/quest/m1/data-consumer-timestamps.md) - json and binary consumers return each value's timestamp, in Rust and every binding; snapshots add `latest()` beside an in-order `next()`
- [JS data consumer timestamps](/quest/m1/js-data-consumer-timestamps.md) - @moq/json and @moq/binary consumers return each value's timestamp, with snapshot `next()` and `latest()`
- [Nested data configs](/quest/m1/data-config-nesting.md) - docs nest `BinaryConfig`/`JsonConfig` in an application section instead of flattening it
Expand Down
4 changes: 4 additions & 0 deletions quest/m1/cache-wall-eviction.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ instead of only on a write. The pool's idle expiry is out of scope.
- Weigh the semantics too: `max_age` is media time on purpose, so a congestion
stall cannot age content out (`track::Info::max_age`). A wall term changes
that for a stalled but live publisher.
- Whatever this decides, an untimed group is never media-stale and only the
pool's expiry reclaims it, as decided in
[moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md).
- Record the numbers and the decision in the PR, then rewrite this quest into
the implementation or delete it.

Expand All @@ -43,3 +46,4 @@ Public API: none from the plan. Wire: none.

- [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - relay memory past the expiry window, in the same cache
- [Cache shard](/quest/m2/cache-shard.md) - the pool's shared counters under many workers
- [moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md) - untimed groups are never media-stale
8 changes: 4 additions & 4 deletions quest/m1/data-consumer-timestamps.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ state carries the timestamp of the frame that produced it.
Decided: `next()` and `poll_next()` return `Timed<T>`, the type the producers
take in [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md).
`at` is the frame's media timestamp on the track's timescale, and `None` for
an untimed frame: [Plan: untimed objects](/quest/m1/plan-untimed-objects.md)
settled (2026-10-01) that absence survives the wire rather than becoming
arrival time. A republisher passes `at` straight to a moq-mux data producer.
an untimed frame: absence survives the wire rather than becoming arrival
time (decided 2026-10-01, see
[moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md)). A republisher passes `at` straight to a moq-mux data producer.

Decided (2026-10-01): snapshot consumers get two reads, both returning
`Timed<T>`. Today the moq-json snapshot consumer applies every buffered delta
Expand All @@ -48,4 +48,4 @@ Public API: breaking. Wire: none.
## Required

- [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md) - gives `Timed.at` its untimed meaning, the type this returns
- [Plan: untimed objects](/quest/m1/plan-untimed-objects.md) - the model must carry an absent timestamp to consumers
- [moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md) - the model must carry an absent timestamp to consumers
2 changes: 1 addition & 1 deletion quest/m1/js-data-consumer-timestamps.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Public API: breaking. Wire: none.
## Required

- [JS publishing never invents a timestamp](/quest/m1/js-publish-timestamp.md) - adds `Timed<T>` to `@moq/net`
- [Plan: untimed objects](/quest/m1/plan-untimed-objects.md) - js/net stops filling arrival time, so `at` can be absent
- [@moq/net carries untimed frames faithfully](/quest/m1/js-untimed-model.md) - js/net stops filling arrival time, so `at` can be absent

## Related

Expand Down
2 changes: 1 addition & 1 deletion quest/m1/js-publish-timestamp.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ Public API: breaking. Wire: none.

## Required

- [Plan: untimed objects](/quest/m1/plan-untimed-objects.md) - an untimed payload must travel as untimed before producers stop filling in now
- [@moq/net carries untimed frames faithfully](/quest/m1/js-untimed-model.md) - the model must hold an untimed payload before producers stop filling in now
42 changes: 42 additions & 0 deletions quest/m1/js-untimed-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# [M] @moq/net carries untimed frames faithfully

## Goal

`@moq/net` mirrors [moq-net carries untimed frames
faithfully](/quest/m1/untimed-model.md). A frame or datagram without a
timestamp stays untimed from publisher to consumer, and js/net never fills in
`Timestamp.now()` on receive. Covers `@moq/net` and its in-repo callers
(`@moq/hang`, `@moq/loc`, `@moq/watch`).

## Plan

Decided (2026-10-02): a hand-written change now, rather than
waiting for [Generated @moq/net](/quest/m1/rs2ts/README.md). That line is
long-running and would stall the JS timestamp quests. Whichever lands second
absorbs the other. The semantics, the end-marker rule and the reasons are in
the Rust quest; keep the two in step.

Things to look out for:

- Receive-side fills: lite frames on a track with no timescale, and IETF
objects without a timestamp, both default to now today.
- The track's drift, reach, staleness and expiry logic skips unstamped groups
the way Rust does. Check that untimed frames still count as frames, and
that an untimed track starts at the latest group.
- A track's `Info.timescale` is required today, with a default. Like Rust,
a track that never declared one must not claim a timeline downstream.
- A FETCH keeps timestamps whenever the track or the object gives units.
Only objects with neither are untimed.
- `Frame.timestamp` and `Datagram.timestamp` become optional. Update callers
in js/hang and js/loc (end markers) and anything in js/watch that reads
them.

Test: an untimed frame survives a JS subscribe on each receive path. Run
`just test interop --all`.

Public API: breaking. Wire: none.

## Related

- [moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md) - the Rust side and the decisions
- [Generated @moq/net](/quest/m1/rs2ts/README.md) - will replace this code with generated code
45 changes: 45 additions & 0 deletions quest/m1/lite-untimed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# [S] lite-07 encodes an absent timestamp

## Goal

On moq-lite-07, an untimed frame or datagram crosses the wire as untimed in
both Rust and JS, so absence survives a lite relay hop. lite-05 and lite-06
keep writing the encoder's send time, documented as a downgrade for old
peers. Covers `rs/moq-net`, `js/net` and `drafts/draft-lcurley-moq-lite.md`.

## Plan

Decided (2026-10-01, maintainer): shift the FRAME Timestamp Delta and the
DATAGRAM Timestamp by one, so 0 means absent. An absent frame doesn't move
the delta baseline. Rejected: a bare 0 as a sentinel, which collides with a
real pts of 0.

Decided (2026-10-02): one PR for both languages, after both
model quests. Shipping one language first would break Rust-JS interop on
lite-07-wip in between.

Draft work:

- The draft describes only the current wire. Add the lite-07 encoding, the
lite-05/06 downgrade, and a lite-07 changelog entry.
- Fix the text that assumes every frame is timed: "each frame carries a
presentation timestamp", the datagram's "any varint value (including 0) is
valid", and expiration's "reach" (the first frame timestamp of the next
group) for an untimed group.
- Check how a lite track that has no timescale says so on lite-07.
- lite-07 varints carry the full 64 bits, so the shift can't represent the
top value: a DATAGRAM Timestamp of 2^64-1, or a delta whose zigzag
encoding is 2^64-1. Wrapping would turn either into the absence marker.
Recommended: the encoder refuses those values and the draft says so,
rather than widening the encoding for timestamps no real track reaches.

Test: an untimed frame and an untimed datagram round-trip Rust-to-JS and
JS-to-Rust on lite-07. lite-06 still receives a timestamp. Run
`just drafts check` and `just test interop --all`.

Public API: none. Wire: lite-07-wip only, which is unpublished.

## Required

- [moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md) - the Rust model must hold an absent timestamp before the wire can carry one
- [@moq/net carries untimed frames faithfully](/quest/m1/js-untimed-model.md) - the same for JS
60 changes: 60 additions & 0 deletions quest/m1/mux-data-timestamp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# [S] moq-mux data producers take a broadcast-clock timestamp

## Goal

A moq-mux JSON or binary data producer accepts a timestamp on the broadcast
clock and publishes it as given, with no ahead-of-now refusal. That timestamp
may be the source's own timestamp on a catalog clock anchored to that source
(KLV next to video from one MPEG-TS program), or the `at` of a consumer's
`Timed` the value was derived from (MAVLink telemetry republished). Callers that hold a capture
`Instant` convert it with a public `Clock::capture`.

Out of scope: what `at: None` means, which
[Publishing never invents a timestamp](/quest/m1/publish-timestamp.md)
changes, along with moq-json, moq-flate, moq-ffi and the
bindings.

## Plan

Requested by OneTooMany (Discord), who align KLV and MAVLink-derived
telemetry with video. Split out of `publish-timestamp` (2026-10-02) because
it needs neither untimed frames nor the FFI namespace move, so it can land
well before the rest.

Decided (2026-10-02):

- `Snapshot::update` / `Stream::append` in `json` and `binary` take
`Timed<_, Timestamp>` instead of `Timed<_, Instant>`. That's the same
contract as `container::Frame`, and the default `Timed` that moq-json and
moq-flate already take. `at: None` keeps stamping the broadcast clock's now
until `publish-timestamp` makes it untimed.
- `Clock::capture(Instant)` becomes public. It keeps refusing an instant
ahead of now or before the epoch (`Error::InvalidCapture`).
- Carried over from `publish-timestamp` (2026-10-01): a timestamp ahead of
now is recorded as zero delay rather than refused. A source clock running
slightly fast must not be rejected.

Things to look out for:

- An ahead-of-now sample can drag the broadcast-wide delay minimum down and
inflate delay or jitter on other tracks. Decide "ahead of now" against the
catalog clock's current reading. Don't clamp the estimator's signed
lateness itself: it is measured from an arbitrary local epoch, where valid
samples can be negative. Test it.
- Callers inside the repository (moq-ffi, moq-c) pass bare values and
should compile unchanged. The changes are moq-mux's own tests and doc
examples that use `.at(Instant)`.
- Document on the producers that a carried-over timestamp lines up with
media only on a broadcast sharing the source's clock mapping. Update
`doc/lib/rs/moq-mux.md`, which describes the `Instant` input and the
refusal.

Test: a source timestamp on a catalog clock anchored to that source is
published unchanged. A timestamp ahead of now is accepted, and other tracks' delay
doesn't move.

Public API: breaking. Wire: none.

## Related

- [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md) - changes what `at: None` means for these producers, after this lands
40 changes: 40 additions & 0 deletions quest/m1/plan-ts-pes-untimed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# [S] Plan: untimed verbatim PES

## Goal

Decide how a verbatim MPEG-TS track carries a PES that has no PTS without
inventing a time, from import through export. The usual case is asynchronous
private data such as KLV. The output is an implementation quest, or a
recorded reason to keep today's behaviour.

## Plan

Today the importer stamps a PES without a PTS at the track's live edge, or
at 0 before the first frame. `drafts/draft-lcurley-moq-mpegts.md` says 0.
Both are invented times, which
[moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md) rules
out for moq-net itself.

Making the moq-net timestamp optional isn't enough on its own. Verbatim
tracks use the legacy container, whose payload always starts with a
timestamp, and its decoder reads the time from there, not from the frame. So
the decision covers:

- **Carriage:** either the legacy payload keeps a time, or verbatim tracks
move to a container that takes its time from the moq-net frame. Weigh what
the second costs existing catalogs and consumers of the `mpegts` section.
- **Export:** how the TS exporter schedules and writes an untimed PES, which
goes out with no PTS.
- **Stream types:** which ones this applies to. A media elementary stream
may split an access unit across PES packets or infer timing. There, a
missing PTS doesn't mean untimed.

The maintainer decides the carriage, because it touches the catalog and
container contract.

Test plan for the implementation quest: importer to encoded track to
exporter, for a PES without a PTS and for one with a real PTS of 0.

## Required

- [moq-net carries untimed frames faithfully](/quest/m1/untimed-model.md) - a frame must be able to carry no timestamp
49 changes: 0 additions & 49 deletions quest/m1/plan-untimed-objects.md

This file was deleted.

Loading
Loading