В get_posts при случайной сортировке не работает пагинация если указать аргумент exclude

Решил я значит по тестить случайную сортировку в get_posts (просьба заказчика) и с удивлением обнаружил что при ajax загрузке происходит дублирование записей, немного покумекав прикинул что это вполне ожидаемое поведение, так как в запросе нет данных о том что уже выведено.

Далее нашел самый очевидный и банальный способ решения, передавать в запросе ID постов которые уже отрисованы и передавать их в параметр 'exclude', логично скажете вы и я соглашусь с вами но в этот момент опять же с удивлением я заметил что теперь в принципе сломалась пагинация, 1 страница отдает лимит вторая же вообще ничего не возвращает.

Причем что странно так это то что если уменьшить лимит для второй страницы (сделать меньше чем кол-во которое осталось), то есть если всего 20 и мы уже показали 12 то сделав лимит меньше 8 он начинает что то показывать, 20-12 это 8 и при лимите в 7 он отдает 1 запись,

В общем пока что не понял с чем это связано, ожидаемое поведение это то что он на 2 странице будет отдавать 8 записей которых нет на первой и всё, видимо пока что придется в ручную делать пагинацию, через offset

Вот пример кода если кому интересно

$exclude_ids = [1212, 616, 773, 3345] // Всего 12 элементов;

$posts = get_posts([
  'paged'            => 2,
  'posts_per_page'   => 12,
  'post_status'      => 'publish',
  'post_type'        => 'news',
  'category'         => 'media',
  'order'            => 'ASC',
  'orderby'          => 'rand',
  'suppress_filters' => true,
  'post__not_in'     => $exclude_ids,
]);

UPD1: Кажется допер, у меня в голове как то по другому строился принцип фильтрации, мол то что мы сначала берем вторую страницу и из неё исключаем exclude а тут вон оно как, он же сначала фильтрует а потом берет 2 страницу, логично что в этом случае её уже нет так как мы исключили 12 записей и осталось 8, интересно как тогда решать проблему с задваиванием постов если через exclude не получается...

UPD2: Нашел ответ, нейронка посоветовала генерить уникальный seed при каждой загрузке страницы и отправлять его в запросе, что бы там сохранился тот же случайный порядок, интересно как бы я пришел к этому варианту не будь под рукой ИИ, в интересное время живем однако, без неё пришлось бы часами лазить по гуглу или ждать пока тут или на хабре ответят

Пример кода

// В functions.php или в файле плагина

// 1. Генерируем случайный порядок один раз при первой загрузке
function custom_random_pagination_seed() {
	if (!isset($_SESSION['random_seed'])) {
		$_SESSION['random_seed'] = rand();
	}
	return $_SESSION['random_seed'];
}

// 2. Модифицируем запрос для использования фиксированного случайного порядка
add_action('pre_get_posts', function($query) {
	if ($query->is_main_query() && $query->get('orderby') === 'rand') {
		$query->set('orderby', 'RAND(' . custom_random_pagination_seed() . ')');
	}
});

// 3. В AJAX-обработчике убедитесь, что передаете тот же seed
add_action('wp_ajax_custom_ajax_pagination', 'custom_ajax_pagination');
add_action('wp_ajax_nopriv_custom_ajax_pagination', 'custom_ajax_pagination');
function custom_ajax_pagination() {
	// Получаем seed из сессии или из запроса
	$seed = isset($_POST['seed']) ? intval($_POST['seed']) : custom_random_pagination_seed();

	$args = array(
		'post_type' => 'post',
		'orderby' => 'RAND(' . $seed . ')',
		'paged' => $_POST['page'],
		'post_status' => 'publish'
	);

	$query = new WP_Query($args);
	// ... остальной код обработки AJAX
	wp_die();
}

Только чутка подредактирую, так как иначе $seed вообще не будет меняться до закрытия браузера, хотя я теперь задумался о том зачем вообще что-то хранить в сессии если после перезагрузки нужен новый $seed, по идее его можно так отправлять в ajax запросе

function custom_random_pagination_seed(): int {
  session_start();

  $_SESSION['random_seed'] = (int)str_pad(rand(), 10, '0');
  return $_SESSION['random_seed'];
}
Заметки к вопросу:
Kama месяц назад

Спасибо за описание здесь интересного решение проблемы рандомной сортировки. Это в заметки можно вынести, позже может перенесу с небольшими правками.