-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())}.