M0: план разработки, техническая структура, каркас проекта
- docs/PLAN.md — план по этапам, обновляется по мере работы - docs/TECHNICAL.md — слои, карта папок, контракты, рабочие процессы - docs/DECISIONS.md — журнал решений, принятых самостоятельно - разрешение 1080x1920, fit-width (портрет, 4:3..21:9) - каркас assets/scripts (core / modules / app / ui) - headless-тесты на vitest + strict typecheck + защита чистоты core - Rng: детерминированный генератор и перемешивание Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { Rng } from '@core/util/Rng';
|
||||
|
||||
describe('Rng', () => {
|
||||
it('is deterministic for a given seed', () => {
|
||||
const a = Array.from({ length: 20 }, () => new Rng(42).next());
|
||||
expect(new Set(a).size).toBe(1);
|
||||
|
||||
const first = new Rng(7);
|
||||
const second = new Rng(7);
|
||||
for (let i = 0; i < 100; i++) {
|
||||
expect(first.next()).toBe(second.next());
|
||||
}
|
||||
});
|
||||
|
||||
it('produces different streams for different seeds', () => {
|
||||
const a = new Rng(1);
|
||||
const b = new Rng(2);
|
||||
const differs = Array.from({ length: 10 }, () => a.next() !== b.next());
|
||||
expect(differs.some(Boolean)).toBe(true);
|
||||
});
|
||||
|
||||
it('resumes from a captured state', () => {
|
||||
const source = new Rng(123);
|
||||
for (let i = 0; i < 5; i++) source.next();
|
||||
|
||||
const resumed = Rng.fromState(source.state);
|
||||
for (let i = 0; i < 20; i++) {
|
||||
expect(resumed.next()).toBe(source.next());
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps nextInt inside its bound', () => {
|
||||
const rng = new Rng(99);
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
const v = rng.nextInt(36);
|
||||
expect(v).toBeGreaterThanOrEqual(0);
|
||||
expect(v).toBeLessThan(36);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects a non-positive bound and an empty pick', () => {
|
||||
const rng = new Rng(1);
|
||||
expect(() => rng.nextInt(0)).toThrow(RangeError);
|
||||
expect(() => rng.pick([])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
it('shuffles as a permutation, not a resample', () => {
|
||||
const rng = new Rng(2024);
|
||||
const original = Array.from({ length: 36 }, (_, i) => i);
|
||||
const shuffled = rng.shuffle([...original]);
|
||||
|
||||
expect(shuffled).toHaveLength(36);
|
||||
expect([...shuffled].sort((x, y) => x - y)).toEqual(original);
|
||||
expect(shuffled).not.toEqual(original);
|
||||
});
|
||||
|
||||
it('shuffles reproducibly', () => {
|
||||
const deck = () => Array.from({ length: 36 }, (_, i) => i);
|
||||
expect(new Rng(5).shuffle(deck())).toEqual(new Rng(5).shuffle(deck()));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user