// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Core.Random
{
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using ProtoBuf;
///
/// BlastCircuit: a four-word ARX-style generator with rotational feedback.
///
///
///
/// Reference: Will Stafford Parsons (wileylooper/blastcircuit, repository offline). The generator keeps four 64-bit state words, mixes them
/// using xor, rotation, and Weyl-style increments, and produces a 64-bit mix value each round (we emit the
/// lower 32 bits in to match the framework API).
///
/// Pros:
///
/// - Moderate state (256 bits) with lively dynamics and excellent bulk generation speed.
/// - Supports deterministic capture with .
///
/// Cons:
///
/// - Not designed for cryptography.
/// - First few outputs after seeding may be correlated—discard them when mirroring the C reference.
///
/// When to use:
///
/// - Procedural content where you want energetic bit diffusion from a compact state.
///
/// When not to use:
///
/// - Security-sensitive systems or scenarios requiring strict statistical certification.
///
///
///
///
/// using WallstopStudios.UnityHelpers.Core.Random;
///
/// BlastCircuitRandom rng = new BlastCircuitRandom(Guid.NewGuid());
/// ulong wide = rng.NextUlong(); // 64-bit output via AbstractRandom
/// int range = rng.Next(0, 100);
///
///
[RandomGeneratorMetadata(
RandomQuality.Good,
"Empirical PractRand testing to 32GB shows strong diffusion; designed as a chaotic ARX mixer rather than a proven statistically optimal generator.",
"Will Stafford Parsons",
"" // Original repository wileylooper/blastcircuit is offline
)]
[Serializable]
[DataContract]
[ProtoContract]
public sealed class BlastCircuitRandom : AbstractRandom
{
private const ulong Increment = 111_111_111_111_111UL;
private const ulong GoldenGamma = 0x9E3779B97F4A7C15UL;
private const int PayloadByteCount = sizeof(ulong) * 2;
public static BlastCircuitRandom Instance => ThreadLocalRandom.Instance;
public override RandomState InternalState
{
get
{
byte[] payload = new byte[PayloadByteCount];
BinaryPrimitives.WriteUInt64LittleEndian(payload.AsSpan(0, sizeof(ulong)), _c);
BinaryPrimitives.WriteUInt64LittleEndian(
payload.AsSpan(sizeof(ulong), sizeof(ulong)),
_d
);
return BuildState(_a, _b, payload);
}
}
[ProtoMember(6)]
private ulong _a;
[ProtoMember(7)]
private ulong _b;
[ProtoMember(8)]
private ulong _c;
[ProtoMember(9)]
private ulong _d;
public BlastCircuitRandom()
: this(Guid.NewGuid()) { }
public BlastCircuitRandom(Guid guid)
{
InitializeFromGuid(guid);
}
public BlastCircuitRandom(ulong seed)
{
InitializeFromScalar(seed);
}
public BlastCircuitRandom(ulong seedA, ulong seedB, ulong seedC, ulong seedD)
{
SetState(seedA, seedB, seedC, seedD);
}
[JsonConstructor]
public BlastCircuitRandom(RandomState internalState)
{
_a = internalState.State1;
_b = internalState.State2;
(ulong payloadC, ulong payloadD) = ReadPayload(internalState.PayloadBytes);
_c = payloadC;
_d = payloadD;
RestoreCommonState(internalState);
}
public override uint NextUint()
{
unchecked
{
ulong mix = _a ^ _b;
_a += Increment;
_b = (_b >> 3) + _c;
_c = _d;
_d = RotateLeft(_d, 21) + mix;
return (uint)mix;
}
}
public override IRandom Copy()
{
return new BlastCircuitRandom(InternalState);
}
private void InitializeFromGuid(Guid guid)
{
(ulong seed0, ulong seed1) = RandomUtilities.GuidToUInt64Pair(guid);
ulong mixed0 = Mix64(seed0);
ulong mixed1 = Mix64(seed0 + GoldenGamma);
ulong mixed2 = Mix64(seed1);
ulong mixed3 = Mix64(seed1 + GoldenGamma);
SetState(mixed0, mixed1, mixed2, mixed3);
}
private void InitializeFromScalar(ulong seed)
{
unchecked
{
ulong baseSeed = Mix64(seed);
ulong bSeed = Mix64(seed + GoldenGamma);
ulong cSeed = Mix64(seed + (GoldenGamma * 2));
ulong dSeed = Mix64(seed + (GoldenGamma * 3));
SetState(baseSeed, bSeed, cSeed, dSeed);
}
}
private void SetState(ulong a, ulong b, ulong c, ulong d)
{
_a = a;
_b = b;
_c = c;
_d = d;
}
private static (ulong, ulong) ReadPayload(IReadOnlyList payload)
{
if (payload == null || payload.Count < PayloadByteCount)
{
return (0UL, 0UL);
}
if (payload is byte[] payloadArray)
{
ulong cValue = BinaryPrimitives.ReadUInt64LittleEndian(
payloadArray.AsSpan(0, sizeof(ulong))
);
ulong dValue = BinaryPrimitives.ReadUInt64LittleEndian(
payloadArray.AsSpan(sizeof(ulong), sizeof(ulong))
);
return (cValue, dValue);
}
Span buffer = stackalloc byte[PayloadByteCount];
for (int i = 0; i < PayloadByteCount; ++i)
{
buffer[i] = payload[i];
}
ulong c = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(0, sizeof(ulong)));
ulong d = BinaryPrimitives.ReadUInt64LittleEndian(
buffer.Slice(sizeof(ulong), sizeof(ulong))
);
return (c, d);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mix64(ulong value)
{
unchecked
{
value += GoldenGamma;
value = (value ^ (value >> 30)) * 0xBF58476D1CE4E5B9UL;
value = (value ^ (value >> 27)) * 0x94D049BB133111EBUL;
value ^= value >> 31;
return value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong RotateLeft(ulong value, int count)
{
return (value << count) | (value >> (64 - count));
}
}
}