Random numbers: seed, state and weighted selection
A reproducible field, independent generators, State restoration and weight validation.
Replay generation
Attach RandomField2D.cs to an Entity2D root in a separate 1.0.0 C# level. Set the window/viewport to 640×420, build and press F6. The 96-marker field repeats with Seed = 42 and the same call order. A different Seed changes it. This chooses points; it does not spawn physics gameplay objects.
RandomField2D.cs
using System;
using Reckfell;
public partial class RandomField2D : Entity2D
{
[Export] public ulong Seed { get; set; } = 42;
[Export] public int Count { get; set; } = 96;
public int PointCount => _points.Length;
public ulong FinalState { get; private set; }
private Vector2[] _points = [];
private int[] _kinds = [];
private Label? _status;
public Vector2 GetPoint(int index) => _points[index];
public int GetKind(int index) => _kinds[index];
public static bool ValidWeights(float[]? weights)
{
if (weights is null || weights.Length == 0) return false;
float sum = 0;
foreach (float weight in weights)
{
if (!float.IsFinite(weight) || weight < 0) return false;
sum += weight;
}
return float.IsFinite(sum) && sum > 0;
}
public static int Choose(RandomNumberGenerator rng, float[] weights)
{
if (!ValidWeights(weights)) throw new ArgumentException("Weights require a finite positive sum", nameof(weights));
return checked((int)rng.RandWeighted(weights));
}
public Error Generate(ulong seed, int count)
{
if (count < 1 || count > 256) return Error.InvalidParameter;
Vector2[] points = new Vector2[count];
int[] kinds = new int[count];
using var rng = new RandomNumberGenerator { Seed = seed };
float[] weights = [1, 3, 6];
for (int i = 0; i < count; i++)
{
points[i] = new Vector2(rng.RandfRange(40, 600), rng.RandfRange(125, 380));
kinds[i] = Choose(rng, weights);
}
FinalState = rng.State;
_points = points;
_kinds = kinds;
Seed = seed;
Count = count;
if (_status is not null) _status.Text = "Seed = " + seed + "; n = " + count;
QueueRedraw();
return Error.Ok;
}
public override void _Ready()
{
bool ru = TranslationServer.GetLocale().StartsWith("ru");
var title = new Label { Position = new Vector2(24, 20), Text = ru
? "Один seed и порядок вызовов — одно поле" : "Same seed and call order, same field" };
title.AddThemeFontSizeOverride("font_size", 22); AddChild(title);
_status = new Label { Position = new Vector2(24, 58) }; AddChild(_status);
AddChild(new Label { Position = new Vector2(24, 85), Text = ru
? "Размеры маркеров: веса 1 : 3 : 6; это не точные количества."
: "Marker sizes use weights 1 : 3 : 6, not exact counts." });
if (Generate(Seed, Count) != Error.Ok) ReckfellApi.PrintErr("RandomField2D: count must be 1..256");
}
public override void _Draw()
{
DrawRect(new Rect2(0, 0, 640, 420), new Color(0.035f, 0.04f, 0.05f));
for (int i = 0; i < _points.Length; i++)
DrawCircle(_points[i], 2 + 2 * _kinds[i], _kinds[i] == 0 ? Colors.White : new Color(0.25f, 0.46f, 1));
}
}Each RandomNumberGenerator owns its state. Its constructor calls Randomize; the example immediately assigns Seed for a defined start. Another instance's calls do not alter this sequence. Separate instances with controlled call order are useful for independent gameplay systems. A global sequence can change because of an unrelated call elsewhere in the project.
Seed and State serve different purposes
Seed restarts a sequence. State resumes it from a saved point. Save State, obtain two numbers, restore State and repeat the same two calls: values should match. The engine fixture checks this case.
If saving both values, restore Seed before State: assigning Seed resets internal state. Do not substitute an arbitrary seed for State. Replay requires the same generator implementation, parameters, call order and branching. A matching seed alone does not establish network synchronization or identical output in future versions.
Ranges and weights
| Method | Result |
|---|---|
| RandiRange(a, b) | Integer including both a and b |
| Randf / RandfRange | Float in the selected range; not a ready-made array index |
| Randfn(mean, deviation) | Normal distribution, not a bounded interval |
| RandWeighted(weights) | Index of the selected weight, not its value |
For a nonempty array index, use RandiRange(0, Length − 1). Randfn can exceed mean ± deviation; clamping changes its distribution. Weights 1 : 3 : 6 give relative probabilities of 10%, 30%, 60%, not exact counts in every hundred draws.
The example's ValidWeights rejects empty input, negative/nonfinite weights, sum overflow and zero total. Native RandWeighted logs an error and returns −1 for empty/negative input; a zero total selects nothing. Do not use −1 as an index. A zero weight in an otherwise valid set excludes that option.
Use this generator for gameplay randomness. Secrets and tokens require a cryptographic generator. Documentation checks establish ranges, replay and selected weight cases; they do not certify statistical quality or bitwise cross-platform determinism.