Files
skills/docs/foundation/random/pseudo-random/src/algorithms/counter-rng.md
2026-05-29 08:20:56 +01:00

1.9 KiB

algorithms/counter-rng

Purpose

CounterRng is a hash-based PRNG: its output at logical position i is splitmix64(seed XOR i). Because the output depends only on position, every seek is constant time — at(1_000_000) does not draw a million values, it just advances the counter. This makes CounterRng uniquely suited to massively parallel simulations, tile-based procedural generation, and any scenario where you need to sample an arbitrary index of the stream without replaying it.

Features

  • O(1) at(offset) and skip(n) — no per-step iteration cost
  • 64-bit seed state stored as two uint32 halves for JSON safety
  • Full SeededRng contract
  • Output quality on par with splitmix64 (passes BigCrush on the upper 32 bits)

Usage: Basic Example

import { CounterRng } from "@suaveplan/pseudo-random";

const rng = new CounterRng("world");
const v = rng.nextUint32();

Usage: Advanced Example

import { CounterRng } from "@suaveplan/pseudo-random";

// Sample tile (1234, 5678) without materialising everything in between.
const rng = new CounterRng("map");
const tile = rng.at(1234 * 10000 + 5678);
const decoration = tile.nextUint32();

API Reference

  • new CounterRng(seed) — accepts the standard string | number seed.
  • nextUint32() / nextFloat() — one counter step, output is splitmix64(seed XOR pos).out >> 32.
  • skip(n) / at(offset) — both constant-time.
  • fork(id) — derives a new counter keyed on (seed, position, id).
  • checkpoint() / restore(cp) — state is [seedLo, seedHi] plus counter.

Implementation Notes

Counter generators expose their index directly, so the usual "position = draws so far" semantics hold regardless of whether you reach position 1000 by calling nextUint32 1000 times, calling skip(1000), or jumping via at(1000). The streamAt helper is deterministic and stateless, which is what makes the O(1) seek possible.