diff --git a/ROADMAP.md b/ROADMAP.md index 9315be8a6..37ef74b55 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,6 +2,8 @@ **Stage: alpha** · next: beta, ~20 points and ~120–210 h away +> **Last reviewed:** 2026-11-04 · **Last updated:** 2026-11-04 · **Change:** trivial (Ogg Opus recovery includes trailing packets without granules in its reported duration; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) + > **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-03 · **Change:** trivial (community PR wave 10: PPTX and native saves keep table cell centred anchoring, text overflow, compatible line spacing, normalised character height, Thai distributed alignment, RTL table direction, alternate run language and reflection transforms; imported effects placed by parent time-node delays; Reuse Slides fits the destination size and brings custom table styles; deleted slides leave custom shows; merged shapes drop their animations; Compress Pictures measures grouped pictures at displayed size; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-02 · **Change:** trivial (File › About replaces Account; product links, credits and appearance retained; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-01 · **Change:** trivial (locked No Fill and no-outline click-through regression coverage added; No Fill hit-testing follows resolved fill instead of the presence of a style (#1621); scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) @@ -150,6 +152,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | What landed | |---|---| +| 2026-11-04 | Ogg Opus recovery keeps trailing packets without granules in the total duration, matching their recovered positions; valid end trimming stays covered | | 2026-11-02 | Native and PPTX saves retain optional table right-to-left direction, including explicit false and omission; two-save round-trip tests keep labeled columns in order | | 2026-11-02 | Text runs preserve alternate-language metadata independently from the main language through native and PPTX saves | | 2026-11-02 | Picture compression measures displayed dimensions through nested group transforms; checklist score unchanged | @@ -330,6 +333,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | Change | Summary | |---|---|---| +| 2026-11-04 | trivial | Ogg Opus recovery duration includes its recovered tail; parity scores unchanged | | 2026-11-02 | trivial | Progress log: run alternate-language metadata round-trips through native and PPTX formats; product parity scores unchanged | | 2026-11-03 | trivial | Reuse Slides brings referenced custom table styles along; scores unchanged | | 2026-11-02 | trivial | File › About replaces Account; product links, credits and appearance retained | diff --git a/crates/ogg/src/lib.rs b/crates/ogg/src/lib.rs index 719c96698..0261ee0e6 100644 --- a/crates/ogg/src/lib.rs +++ b/crates/ogg/src/lib.rs @@ -513,6 +513,7 @@ impl OpusTiming { c = timing_add(c, i64::from(durations[k]))?; ends[k] = Some(c); } + cursor = Some(c); break; } let Some(g) = stream.packets[j].granule else { break }; diff --git a/crates/ogg/src/tests.rs b/crates/ogg/src/tests.rs index 8f9673089..728e618bd 100644 --- a/crates/ogg/src/tests.rs +++ b/crates/ogg/src/tests.rs @@ -183,6 +183,27 @@ fn opus_toc_durations() { assert_eq!(opus_packet_samples([0xF8, 0], 0), None); } +#[test] +fn opus_timing_includes_packets_after_the_last_known_granule() { + let f = opus_file(6, 312, 0); + let o = open(&f).unwrap(); + let mut stream = o.streams[0].clone(); + // Missing granules on complete-packet pages are non-conforming. The existing recovery + // fallback still assigns their packet positions; total must include the same samples. + stream.packets.last_mut().unwrap().granule = None; + let timing = OpusTiming::of(&stream, 312).unwrap(); + assert_eq!(timing.starts, vec![-312, 648, 1608, 2568, 3528, 4488]); + assert_eq!(timing.total, 6 * 960 - 312); + + // With no usable granule, every complete packet still contributes its TOC duration. + for packet in &mut stream.packets { + packet.granule = None; + } + let timing = OpusTiming::of(&stream, 312).unwrap(); + assert_eq!(timing.starts, vec![-312, 648, 1608, 2568, 3528, 4488]); + assert_eq!(timing.total, 6 * 960 - 312); +} + #[test] fn opus_timing_rejects_a_granule_that_overflows() { let packet = |granule: i64| Stream {