const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const sandbox = {}; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../artwork-schedule.js'), 'utf8'), sandbox); const schedule = sandbox.NNCArtworkSchedule; const order = loop => Array.from(schedule.orderForLoop(loop)); test('137-minute slots have a fixed selection up to the exact boundary', () => { const duration = 137 * 60 * 1000; assert.equal(schedule.SLOT_DURATION, duration); const now = Date.parse('2026-10-07T09:00:00+09:00'); const start = Math.floor(now / duration) * duration; const first = schedule.selectionAt(start); assert.equal(schedule.selectionAt(start + 1).variant, first.variant); assert.equal(schedule.selectionAt(start + duration - 1).variant, first.variant); const next = schedule.selectionAt(start + duration); assert.equal(next.slot, first.slot + 1); assert.notEqual(next.variant, first.variant); }); test('PRNG streams and permutations reproduce across fresh evaluations', () => { const fresh = {}; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../artwork-schedule.js'), 'utf8'), fresh); for (const seed of [0, 1, 100, 101, 102, 2147483647]) { const a = schedule.createRandom(seed); const b = fresh.NNCArtworkSchedule.createRandom(seed); for (let index = 0; index < 100; index++) { const value = a(); assert(value >= 0 && value < 1); assert.equal(value, b()); } assert.deepEqual(order(seed), Array.from(fresh.NNCArtworkSchedule.orderForLoop(seed))); } }); test('10,000 loops each contain all 30 artworks with no repeat across boundaries', () => { let previous; for (let loop = 0; loop < 10000; loop++) { const current = order(loop); assert.deepEqual([...current].sort((a, b) => a - b), Array.from({ length: 30 }, (_, i) => i)); if (previous) { assert.notEqual(previous[29], current[0]); assert.notDeepEqual(previous, current); } previous = current; } }); test('a boundary collision swaps exactly the first two items', () => { function raw(loop) { const random = schedule.createRandom(loop); const result = Array.from({ length: 30 }, (_, i) => i); for (let i = 29; i > 0; i--) { const j = Math.floor(random() * (i + 1)); [result[i], result[j]] = [result[j], result[i]]; } return result; } let collisions = 0; for (let loop = 1; loop < 500; loop++) { const expected = raw(loop); if (raw(loop - 1)[29] === expected[0]) { [expected[0], expected[1]] = [expected[1], expected[0]]; collisions++; } assert.deepEqual(order(loop), expected); } assert(collisions > 0); }); test('all slots of a loop map to its permutation, including loop transitions', () => { for (const loop of [0, 100, 101, 102, 2102]) { for (let position = 0; position < 30; position++) { const value = schedule.selectionAt((loop * 30 + position) * schedule.SLOT_DURATION); assert.equal(value.loop, loop); assert.equal(value.position, position); assert.equal(value.variant, order(loop)[position]); } } });