0xFeedBeef 23.976 is only a rounded display value. The actual NTSC-derived frame rate is 24000/1001 ≈ 23.976023976, not 23967/1001 ≈ 23.943056943. So 23967/1001 is mathematically incorrect for standard 23.976 content.
You are correct that MP4 timing is derived from timescales, sample durations, CTS/DTS timing, and sample tables, and that “fps” is often computed rather than explicitly stored as a literal decimal value.
However, using –default-duration 0:${INPUT_FPS}fps is not equivalent to preserving original MP4 presentation timing. That option synthesizes timing during muxing based on a nominal FPS value, whereas MP4 containers already contain frame/sample timing, presentation cadence, CTS offsets, decode ordering, and timing tables.
The issue I ran into was not “wrong FPS math” but loss of original MP4 timing semantics when working from raw HEVC elementary streams.
Using MP4-derived presentation timestamps during mkvmerge restored Constant 23.976 FPS presentation, Apple-style timing behavior, and proper MediaInfo reporting.
So there is a difference between setting a nominal FPS value and preserving the original presentation timing structure from the MP4.