Current section
Files
Jump to
Current section
Files
src/metamon@generator@seed.erl
-module(metamon@generator@seed).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/metamon/generator/seed.gleam").
-export([state/1, next_int/1, next_int_in/3, split/1, seed/1, random_seed/0]).
-export_type([seed/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Deterministic pseudo-random seed.\n"
"\n"
" The implementation is the 32-bit \"Marsaglia xorshift\" PRNG. It\n"
" uses only shifts and xors — no multiplication — so BEAM (bignum\n"
" integers) and JavaScript (53-bit safe doubles) produce\n"
" bit-identical streams. An LCG-style multiplier would overflow\n"
" JavaScript's double precision well before the 32-bit mask and\n"
" cause subtle distribution drift.\n"
"\n"
" Statistical quality is not the goal here — determinism and\n"
" portability are. Property-based testing is well-served even by\n"
" modest PRNGs because the failure search is shrinker-driven, not\n"
" statistical.\n"
).
-opaque seed() :: {seed, integer()}.
-file("src/metamon/generator/seed.gleam", 45).
?DOC(
" The raw integer state. Used by the regression-file format to\n"
" serialise a seed into a reproduction key.\n"
).
-spec state(seed()) -> integer().
state(S) ->
erlang:element(2, S).
-file("src/metamon/generator/seed.gleam", 92).
-spec step(integer()) -> integer().
step(State) ->
After_first_shift = erlang:'bxor'(
State,
erlang:'band'(erlang:'bsl'(State, 13), 16#FFFFFFFF)
),
After_second_shift = erlang:'bxor'(
After_first_shift,
erlang:'bsr'(After_first_shift, 17)
),
After_third_shift = erlang:'bxor'(
After_second_shift,
erlang:'band'(erlang:'bsl'(After_second_shift, 5), 16#FFFFFFFF)
),
erlang:'band'(After_third_shift, 16#FFFFFFFF).
-file("src/metamon/generator/seed.gleam", 51).
?DOC(
" Advance the seed once and return the next non-negative integer\n"
" alongside the advanced seed.\n"
).
-spec next_int(seed()) -> {integer(), seed()}.
next_int(S) ->
Next_state = step(erlang:element(2, S)),
{Next_state, {seed, Next_state}}.
-file("src/metamon/generator/seed.gleam", 61).
?DOC(
" Return an integer uniformly in the closed interval `[lo, hi]`.\n"
"\n"
" If `lo > hi` the bounds are swapped. If `lo == hi` the bound is\n"
" returned without consuming randomness (the seed is still advanced\n"
" for determinism).\n"
).
-spec next_int_in(seed(), integer(), integer()) -> {integer(), seed()}.
next_int_in(S, Lo, Hi) ->
{Low, High} = case Lo > Hi of
true ->
{Hi, Lo};
false ->
{Lo, Hi}
end,
case Low =:= High of
true ->
{_, S2} = next_int(S),
{Low, S2};
false ->
{Raw, S2@1} = next_int(S),
Span = (High - Low) + 1,
{Low + (case Span of
0 -> 0;
Gleam@denominator -> Raw rem Gleam@denominator
end), S2@1}
end.
-file("src/metamon/generator/seed.gleam", 84).
?DOC(
" Split a seed into two statistically independent seeds. The\n"
" implementation derives the second seed by xor-ing the first with a\n"
" constant before stepping, which on a 32-bit LCG produces a stream\n"
" that does not align with the original — sufficient for shrinking\n"
" independent generator components without correlation artefacts.\n"
).
-spec split(seed()) -> {seed(), seed()}.
split(S) ->
{Left_state, _} = next_int(S),
Xored = erlang:'band'(erlang:'bxor'(Left_state, 16#A5A5A5A5), 16#FFFFFFFF),
{Right_state, _} = next_int({seed, Xored}),
{{seed, Left_state}, {seed, Right_state}}.
-file("src/metamon/generator/seed.gleam", 114).
-spec normalise(integer()) -> integer().
normalise(Value) ->
Positive = case Value < 0 of
true ->
0 - Value;
false ->
Value
end,
Masked = erlang:'band'(Positive, 16#FFFFFFFF),
case Masked of
0 ->
16#DEADBEEF;
N ->
N
end.
-file("src/metamon/generator/seed.gleam", 33).
?DOC(
" Construct a seed from an integer. Negative or out-of-range values\n"
" are normalised by masking to the 32-bit positive window so the\n"
" stream stays target-portable.\n"
).
-spec seed(integer()) -> seed().
seed(Value) ->
{seed, normalise(Value)}.
-file("src/metamon/generator/seed.gleam", 39).
?DOC(
" Construct a seed from the system clock. Useful for ad-hoc local\n"
" runs; CI should pin a value via `metamon.with_seed(metamon.seed(_))`.\n"
).
-spec random_seed() -> seed().
random_seed() ->
{seed, normalise(metamon_ffi:now_microseconds())}.