Skip to content

feat: implement seedable RNG system #5

@edloidas

Description

@edloidas

Implement seedable RNG interface with xorshift128+ implementation and MockRNG for deterministic testing.


Rationale

Deterministic testing of dice rolls requires:

  1. Seedable RNG — Same seed produces identical sequence (reproducible tests)
  2. MockRNG — Return predefined values for exact test control

The RNG interface also enables:

  • Reproducible rolls for debugging
  • User-provided seeds for "fair" rolls
  • Dependency injection in consumer code

References


Things to Consider

  • Bounds: nextInt(min, max) must be inclusive [min, max]
  • Distribution: Verify uniform distribution across range
  • Seed types: Support both string and number seeds
  • Export for consumers: RNG types should be part of public API

Implementation

Important

This is not a step-by-step guide — it's a functional checklist ordered logically.

Files to Create

  1. /src/rng/interface.ts — RNG interface:

    interface RNG {
      next(): number;  // Returns [0, 1)
      nextInt(min: number, max: number): number;  // Returns [min, max] inclusive
    }
  2. /src/rng/xorshift.ts — SeededRNG implementation:

    class SeededRNG implements RNG {
      constructor(seed?: string | number) { /* xorshift128+ state init */ }
      next(): number { /* [0, 1) */ }
      nextInt(min: number, max: number): number { /* [min, max] */ }
    }
  3. /src/rng/mock.ts — MockRNG for testing:

    function createMockRng(values: number[]): RNG {
      let index = 0;
      return {
        next: () => values[index++] / 100,  // Normalize to [0, 1)
        nextInt: (min, max) => values[index++],  // Return exact value
      };
    }
  4. /src/rng/rng.test.ts — Comprehensive RNG tests

Test Cases

// Seedable reproducibility
const rng1 = new SeededRNG('test-seed');
const rng2 = new SeededRNG('test-seed');
expect(rng1.nextInt(1, 6)).toBe(rng2.nextInt(1, 6));

// Distribution coverage
const rng = new SeededRNG(42);
const values = new Set(Array.from({ length: 1000 }, () => rng.nextInt(1, 6)));
expect(values).toEqual(new Set([1, 2, 3, 4, 5, 6]));

// MockRNG determinism
const mock = createMockRng([4, 2, 6]);
expect(mock.nextInt(1, 6)).toBe(4);
expect(mock.nextInt(1, 6)).toBe(2);
expect(mock.nextInt(1, 6)).toBe(6);

// Bounds inclusivity
const boundRng = new SeededRNG(123);
const results = Array.from({ length: 10000 }, () => boundRng.nextInt(1, 20));
expect(Math.min(...results)).toBe(1);
expect(Math.max(...results)).toBe(20);

Acceptance Criteria

  • SeededRNG produces reproducible sequences from seed
  • MockRNG returns exact predefined values in order
  • nextInt(min, max) bounds are inclusive [min, max]
  • Both string and number seeds work
  • Distribution is uniform (statistical test)
  • RNG types exported for library consumers
  • All test cases pass

Drafted with AI assistance

Metadata

Metadata

Assignees

Labels

featureNew functionalitytestingBuild, tests and CI related features and issues

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions