Skip to content

test(audio-quality): record real arrival traces and grade their replay per PR - #4525

Merged
kixelated merged 9 commits into
quest/m0/audio-quality-harness/READMEfrom
quest/m0/audio-quality-harness/traces
Sep 29, 2026
Merged

kixelated merged 9 commits into
quest/m0/audio-quality-harness/READMEfrom
quest/m0/audio-quality-harness/traces

Conversation

@kixelated

@kixelated kixelated commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Records real audio arrival traces, checks them in, and grades a replay of each next to the harness's synthetic profiles, on every PR that touches the player. Completes quest/m0/audio-quality-harness/traces.md and replay-ci.md.

Problem

The harness's shaper profiles are synthetic. The player needs grading against real arrival shapes (the #3477 flush-span bursts), and those traces are gone.

Approach

  • Recorder (test/audio-quality/clients/js/record.ts, src/recorder.ts): headless Chromium subscribes over WebTransport and stamps each audio frame as it comes off its group's stream, which is where Container.Consumer receives it. It uses only public @moq/net + @moq/hang, and stops on the page clock, so a quiet publisher is recorded as silence.
  • Traces (test/audio-quality/traces/, 35 s each, recorded 2026-09-29 with just test audio-quality-record):
    • relay-bbb: cdn.moq.dev/demo bbb.hang. One AAC frame per group, flushed in bursts of about 7 (p95 spread 157 ms).
    • local-aac: the harness's AAC publisher through a local relay, the shallow control.
    • relay-mic: <moq-publish> publishing Chromium's fake capture device through cdn.moq.dev/anon. It includes a real 390 ms stall.
  • Replay (js/watch/src/audio/replay.ts, @internal): writes each recorded arrival into its group on a real Track.Producer and reads it through the player's Container.Consumer at the delay's max age, on a simulated clock. The consumer's group ordering, holds, max-age skips, and discontinuities are the player's own; a discontinuity resets the ring as the decoder does. Delivered frames go into the real SharedRingBuffer / AudioRingBuffer, sized the way the decoder sizes them. The delay comes from a real Sync fed the recorded catalog config and PROBE RTT. target() waits on the delay signal, and the replay orders itself behind the consumer with a microtask barrier, so neither waits on time.
  • Harness: every trace becomes a replay-<codec>-<rate>-<trace>-<ring> row (6 rows, about 2 s). just test audio-quality --replays runs only those and needs only bun: no cargo, ffmpeg, browser, relay, or publishers. Their budgets are exactly what was measured, with no headroom.
  • CI: .github/workflows/audio-quality.yml runs --replays --enforce on PRs touching js/watch, js/hang, js/net, js/signals, test/audio-quality, test/lib, or test/justfile. Nightly still runs the full matrix.

Current replay grades at the "auto" delay:

row underruns/min skips/min target
relay-bbb 0 0 289 ms (no PROBE RTT from the public relay, so fallback 100 + advertised 186 + 3)
local-aac 103.7 2 186 ms
relay-mic 227.8 0 123 ms

Why local-aac moved (from 101.7 underruns/min, 287.3 gap ms/min, 0.004 silence to 103.7, 293.2, 0.005): the local relay delivers each burst newest-group-first (for example 498, 502, 501, 500, 499, then 497). The ring-only replay played the newer frames while 497 was still missing, which let the playhead step past 497's start. The consumer holds the burst until 497 arrives, so the ring runs dry and waits instead. Playback then lands about 3 ms later, and a later gap goes one quantum deeper. relay-bbb and relay-mic are unchanged.

Decisions

Maintainer decisions, 2026-09-29, via /quest-plan:

  1. ✅ Replay scope: through the consumer. Recorded arrivals go through Container.Consumer on the simulated clock, not straight into the ring, so the grade covers the player's real skip and wait decisions. (Alternative: ring-only, documented.) This resolves the Codex P1.
  2. ✅ Author calls accepted:
    • The recorder is a harness page on the public @moq/net/@moq/hang API: no @moq/watch hook, no CLI flag.
    • The replay grades at the "auto" delay from a real Sync, with the 100 ms fallback where the relay reported no PROBE RTT. Revisit when the watch quest moves the target to the arrival estimator.
    • The recorder's wire max age is 10 s, with the 5 s warmup excluded from the grade.
  3. ✅ Late Codex findings: refuse an unknown --profiles value in replay.ts (fixed); grading the silent tail is a follow-up.
  4. ✅ CI: per PR. The replay rows gate PRs touching js/watch and the harness, not only nightly, since they are deterministic and fast. Wired with path filters in a new workflow.

Impact

  • Public API: none. replay.ts is @internal and not reachable through @moq/watch's exports map.
  • Wire: none.
  • Harness CLI: new --replays flag on just test audio-quality.
  • CI: new path-filtered Audio quality / Replay job on PRs.

Alternatives

  • A ring-only replay. It was simpler, but it graded raw ring writes and missed the consumer's decisions (the P1).
  • Folding the replay gate into just check, as replay-ci.md planned. The maintainer chose a path-filtered workflow instead.

Not covered

  • relay-mic uses Chromium's fake capture device, not a real microphone. A headless agent has no mic to capture, and publishing a real one to a public relay needs a person's consent.
  • A trace records no stream FIN, so a group's stream is taken to finish with its last recorded frame. Every checked-in trace has one frame per group.
  • The replay's own subscription passes every recorded group through. The recorder's relay already applied the 10 s wire max age; the player's shorter wire max age is left to the consumer's.
  • Decoding is modeled as instant, and the legacy decoder's three-callback warmup is not modeled.
  • A replay ends at the last arrival, so a silent tail after the publisher stops is not graded (follow-up below).

Follow-ups

  • Grade the silent tail of a recording: add the requested end to Trace and replay up to it, so a publisher that stops early is graded to the end (Codex finding; maintainer deferred it).
  • Re-record relay-mic with a real microphone in the watch quest's manual run (already in quest/m0/audio-jitter-target/watch.md).
  • The questline base is behind main, which has fix(watch): hold video frames and reject older decode groups #4483 (reject older decode groups). Merging main into the questline may move the replay numbers; re-baseline them then.
  • Interop fails on the TS pcr-monotonic check here and on unrelated branches (quest/m1/watch-refusal, quest/m1/interop-audio-cold-start). That is a base failure, not this PR. fix(moq-mux): one TS re-anchor shift per program #4513 may cover it.
  • quest check fails on main: quest/m2/ts-import-health.md links the deleted /quest/m1/srt-import-stats.md. Unrelated.

Checks

just check passes except for that unrelated quest check link. just test audio-quality --replays --enforce grades all 6 rows within budget. The output is byte-identical across runs.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 4 commits September 29, 2026 10:57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records three arrival traces in headless Chromium (the public relay's
bbb.hang, a local relay's AAC tone, and a browser publisher through the
public relay), stamping each frame as it comes off its group's stream.
The harness replays them through the player's real rings on a simulated
clock at the delay Sync resolves, and grades them as exact replay rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…EADME' into quest/m0/audio-quality-harness/traces
@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome, from the background quest agent. It could not ask questions, so it is leaving this as a draft.

Open decisions

  1. Where the recorder lives. Chosen: a harness page on the public @moq/net/@moq/hang API. There is no @moq/watch hook and no CLI flag, so no API changes. Recommend keeping it.
  2. Replay target. Replay rows grade at main's "auto" delay, resolved by a real Sync from the recorded catalog and PROBE RTT. The public relay reported no PROBE RTT, so relay-bbb and relay-mic sit at the 100 ms fallback. Recommend keeping this until the watch quest moves the target onto the arrival estimator.
  3. Recorder wire max age of 10 s. This keeps late groups, but it makes a trace's tune-in heavier than a player's. The 5 s warmup keeps the tune-in out of the grade. Recommend keeping it. The alternative is recording at a player-like max age, which would tie the trace to one delay.

Suggested follow-ups

  • Gate the replay rows per PR, not only nightly. They are deterministic and take seconds, so a regression in the rings or Sync would fail the PR that caused it. Recommended.
  • Re-record relay-mic with a real microphone from a person's machine (Chrome and Safari), as the watch quest's manual run already plans. Recommended as part of that quest.
  • quest check on main fails: quest/m2/ts-import-health.md links the deleted /quest/m1/srt-import-stats.md. This is unrelated to this PR.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review September 29, 2026 18:57
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T21:09:21.376669Z 3004f8a New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ae5051fa1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/audio-quality/clients/js/replay.ts Outdated
Comment on lines +97 to +98
const arrivals = trace.arrivals.map(([at, timestamp]) => ({ at, timestamp }));
for (const quantum of replay(arrivals, { ring: row.ring === "isolated" ? "shared" : "post", rate, delay })) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve group semantics when replaying arrivals

When groups arrive out of order, as they do after warmup in every checked-in trace, this mapping discards the group sequence and replay() inserts each frame directly into the ring. The production decoder instead passes groups through Container.Consumer, which orders them and applies the delay-derived maxAge skip logic. Consequently these budgets grade raw timestamped ring writes rather than the documented player behavior, so they can report a pass while changes to group waiting, skipping, or discontinuity handling regress real playback.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 69c1430, per the maintainer's call (replay scope: through the consumer). replay() now writes each arrival into its group on a real Track.Producer and reads through Container.Consumer at the delay's max age, so group ordering, holds, max-age skips, and discontinuity ring resets are the player's own. A new test drops one group and asserts the ring runs dry while the later groups wait; the ring-only replay played straight through it. local-aac's budgets moved with it (the PR body says why).

(Written by Claude Opus 5.5)

Comment thread test/audio-quality/run.sh
Comment on lines +157 to +160
for profile in "${PROFILES[@]}"; do
if [[ " ${ALL_TRACES[*]} " == *" $profile "* ]]; then
TRACES+=("$profile")
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip browser infrastructure for replay-only selections

When every selected profile is moved into TRACES here, ROWS is empty, but the script still requires cargo and ffmpeg, builds three Rust binaries and the Vite pages, installs Chromium, starts a relay, and launches publishers before reaching the replay section. Thus the newly documented replay-only command can fail on missing browser/live-test tooling and is not the seconds-long deterministic path it advertises; gate the live setup and publishers on ROWS being nonempty.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 69c1430. With no browser rows, run.sh only needs bun: it installs the workspace and replays, skipping cargo, ffmpeg, Playwright, the page build, the relay, and the publishers. --replays selects every trace.

(Written by Claude Opus 5.5)

Comment thread js/watch/src/audio/replay.ts Outdated
delay: input.delay,
probe: input.rtt === undefined ? undefined : { rtt: Time.Milli(input.rtt) },
});
const unregister = sync.register(new Signal<Time.Milli | undefined>(playbackJitter(input.config)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Flush the effect without a wall-clock timer

The new target unit tests always pass through this real setTimeout(0), making completion depend on the host scheduler instead of deterministic effect flushing. Replace it with a mocked/scoped clock or a deterministic signal flush so the test does not wait on wall-clock timing, as required for unit tests in this repository.

AGENTS.md reference: AGENTS.md:L18-L19

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 69c1430. target() now awaits sync.out.delay.changed() until the delay equals the resolved jitter plus the registered rendition's, with no timer. The replay itself uses setImmediate as a microtask barrier after writing arrivals: nothing on the consumer path arms a timer, so it orders the replay behind the consumer rather than waiting on time, and repeated runs write byte-identical output.

(Written by Claude Opus 5.5)

Replays run in seconds without a browser, so they gate PRs while the rest of
the matrix stays nightly. The watch quest's manual run re-records relay-mic
with a real microphone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: caa7635a3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/audio-quality/clients/js/record.ts Outdated
}).then((first) => arrivals.push(...first));

const start = arrivals[0][0];
while ((arrivals.at(-1)?.[0] ?? start) < start + durationMs) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop recording after the requested wall-clock duration

When a broadcast stops producing frames after recording begins but leaves the track open, arrivals.at(-1) never advances, so this loop runs forever even though --duration has elapsed. Temporary outages also make the command wait until the next frame rather than preserving the outage at the end of the trace. Base termination on elapsed wall-clock time from the first arrival so a silent interval is recorded instead of hanging the recorder.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 69c1430. The loop now ends on the page's performance.now(), the arrivals' own clock, and drains once more after it, so a quiet publisher is recorded as silence instead of holding the recording open.

(Written by Claude Opus 5.5)

Comment thread test/audio-quality/clients/js/replay.ts Outdated
.sort()
.map((f) => ({ profile: basename(f, ".json"), trace: JSON.parse(readFileSync(join(dir, f), "utf8")) as Trace }));
const profiles = values.profiles === undefined ? undefined : split(values.profiles);
const rings = split(values.rings) as Ring[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate replay ring selectors before casting

When replay.ts is invoked directly with a misspelled --rings value, this cast accepts it and emits a row bearing that invalid ring name, while line 98 silently runs the postMessage ring for every value other than isolated. For example, --rings typo --list succeeds and a normal run produces mislabeled results before analysis eventually rejects the row key. Validate selectors against isolated and plain at argument parsing time.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 69c1430. replay.ts rejects an unknown ring or codec at parse time and exits 2.

(Written by Claude Opus 5.5)

…on them

The replay now writes each recorded arrival into its group and lets the
player's Container.Consumer order, hold, and skip groups at the delay's max
age, resetting the ring on a discontinuity as the decoder does. local-aac's
budgets move with it: its relay flushes groups newest-first, so the consumer
holds a burst behind its oldest frame and the ring lands one quantum later.

target() waits on the delay signal instead of a timer, run.sh skips the
browser, relay, and publishers when only replays run (--replays), the
recorder stops on the page clock, and replay.ts rejects unknown rings.
.github/workflows/audio-quality.yml grades the replays on PRs touching
js/watch, js/hang, or the harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title test(audio-quality): record real arrival traces and grade their replay test(audio-quality): record real arrival traces and grade their replay per PR Sep 29, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69c14304d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let next = 0;

try {
for (let n = 1; first.at + (n - 1) * step <= last.at; n++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replay until the recording's wall-clock end

When a publisher stops before the requested duration, record.ts now continues polling, but this loop still stops at last.at, and the serialized Trace contains no requested end time. The trailing outage therefore vanishes from replay and from the silence and stall budgets instead of being graded. This is fresh evidence beyond the earlier recorder finding: wall-clock capture was added, but its end is never passed to replay().

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as is. After the last arrival nothing more is delivered, so every quantum past it underruns no matter what the player decides. Grading that tail would score the publisher stopping, not the consumer or the rings. Outages in the middle of a trace stay in, because later arrivals bound them. A requested end would also mean a new Trace field and a re-baseline for a tail that no checked-in trace has (each ends within about 150 ms of its 35 s).

(Written by Claude Opus 5.5)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maintainer decided to leave this for a follow-up: grading the silent tail from the recording's requested end. It's listed under Follow-ups in the PR description.

(Written by Claude Opus 5.5)

Comment on lines +10 to +12
- "js/watch/**"
# The container consumer decides what a replay delivers, waits for, and skips.
- "js/hang/**"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate changes to every replay dependency

The path filter covers js/watch and js/hang but not js/net or js/signals. A PR that only changes Group/Track scheduling or signal effect delivery can change replay results, yet this workflow is skipped; these are direct runtime dependencies because replay.ts imports Group, Track, Time, and Signal. Include those paths so deterministic budget regressions block the affected PR rather than waiting for the nightly run.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e7334db: the workflow also runs on js/net/** and js/signals/**.

(Written by Claude Opus 5.5)

Comment thread test/audio-quality/clients/js/record.ts Outdated
Comment on lines +35 to +36
const durationMs = Number.parseFloat(values.duration) * 1000;
if (!pageDir || !url || !broadcast || !source || !description || !out || !(durationMs > 0)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite recording durations

When invoked with --duration Infinity, Number.parseFloat returns infinity and this validation accepts it, so the wall-clock recording loop never terminates; prefixes such as 35oops are also silently accepted as 35 seconds. Validate the complete value and require it to be finite and positive so malformed manual recording commands fail immediately.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e7334db: --duration is parsed with Number and must be finite and positive, so Infinity and 35oops are rejected.

(Written by Claude Opus 5.5)

…e recording duration

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7334dba23

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

for (const { profile, trace } of traces) {
if (trace.version !== 1) throw new Error(`${profile}: trace version ${trace.version}, expected 1`);
const codec = traceCodec(trace);
if (profiles && !profiles.includes(profile)) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unknown replay profiles

When this CLI is invoked directly with a misspelled selector such as --profiles relay-bb, every trace is skipped here and the command exits successfully without listing or writing any rows. Validate each requested profile against the loaded trace names so a typo cannot silently turn a targeted replay into a no-op.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixing this here. run.sh already rejects an unknown profile before it calls replay.ts (the valid check against the trace names), so this only affects running replay.ts by hand. It's cheap to add if the maintainer wants it.

(Written by Claude Opus 5.5)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d360b87, per the maintainer: replay.ts now refuses a profile that matches no trace and exits 2, the same way it handles rings and codecs.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits September 29, 2026 13:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3004f8a3be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const traces = readdirSync(dir)
.filter((f) => f.endsWith(".json"))
.sort()
.map((f) => ({ profile: basename(f, ".json"), trace: JSON.parse(readFileSync(join(dir, f), "utf8")) as Trace }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate trace sample rates before replay

When --traces contains a malformed trace whose config.sampleRate is missing or nonnumeric, this unchecked cast passes it to playbackJitter, producing NaN. The equality on target() line 74 can then never succeed, and after the delay signal settles, changed() never resolves, so the replay hangs until the 20-minute workflow timeout instead of rejecting the trace. Validate that the sample rate is finite and positive while loading each trace.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

@kixelated
kixelated merged commit 0f024e3 into quest/m0/audio-quality-harness/README Sep 29, 2026
6 of 7 checks passed
@kixelated
kixelated deleted the quest/m0/audio-quality-harness/traces branch September 29, 2026 22:42
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.

1 participant