(() => { 'use strict'; const SLOT_DURATION = 137 * 60 * 1000; // Mulberry32: 같은 Seed는 항상 0 이상 1 미만의 동일한 난수열을 생성합니다. function createRandom(seed) { let state = seed >>> 0; return () => { state = (state + 0x6D2B79F5) >>> 0; let value = Math.imul(state ^ (state >>> 15), state | 1); value ^= value + Math.imul(value ^ (value >>> 7), value | 61); return ((value ^ (value >>> 14)) >>> 0) / 4294967296; }; } function rawOrder(loop, count) { const random = createRandom(loop); const order = Array.from({ length: count }, (_, index) => index); for (let index = count - 1; index > 0; index--) { const other = Math.floor(random() * (index + 1)); [order[index], order[other]] = [order[other], order[index]]; } return order; } function orderForLoop(loop, count = 30) { if (!Number.isInteger(count) || count < 3) throw new RangeError('At least three artworks are required.'); if (!Number.isSafeInteger(loop) || loop < 0) throw new RangeError('Invalid loop number.'); const order = rawOrder(loop, count); if (loop > 0) { // 항목이 3개 이상이면 첫 두 위치를 교환해도 마지막 항목은 바뀌지 않습니다. // 따라서 이전 루프의 보정 전 마지막 항목을 그대로 비교할 수 있습니다. // 재귀 호출, 상태 저장, 과거 루프 전체 순회는 필요하지 않습니다. const previousLast = rawOrder(loop - 1, count)[count - 1]; if (order[0] === previousLast) [order[0], order[1]] = [order[1], order[0]]; } return order; } function selectionAt(time, count = 30) { if (!Number.isFinite(time) || time < 0) throw new RangeError('Invalid timestamp.'); const slot = Math.floor(time / SLOT_DURATION); const loop = Math.floor(slot / count); const position = slot % count; return { slot, loop, position, variant: orderForLoop(loop, count)[position] }; } globalThis.NNCArtworkSchedule = Object.freeze({ SLOT_DURATION, createRandom, orderForLoop, selectionAt }); })();