WeightedRoller¶
Namespace Nfty.Core.Generation in assembly Nfty.Ticket.
Weighted selection with a stable, locale-independent draw order.
Methods¶
AbsentWeight(double, double)¶
The weight of an "absent" outcome that makes a layer miss percent of the time against the variants it competes with.
Parameters
percent(double) - How often the layer is left out, 0..100.variantTotal(double) - The total weight of the layer's variants.
Returns. A weight to hand WeightedRoller.Roll().
Exceptions
ArgumentOutOfRangeException- The percent is outside 0..100, or is 100 — which has no finite weight and must be handled by not rolling.
Remarks
Solving a / (a + W) = p for a gives W · p / (1 − p). The conversion from a percent happens HERE and nowhere else, which is the reason the manifest stores a percent rather than a probability.
At 100 the formula divides by zero, and the caller must not reach it: a layer that never appears is not rolled at all. WeightedRoller.AlwaysAbsent() is that test, so the check and the formula cannot drift apart.
AlwaysAbsent(double)¶
Whether a layer with this absent percent is never rolled at all.
Parameters
percent(double) - How often the layer is left out, 0..100.
Returns. True at 100 or above — shelved, the same meaning a recipe weight of 0 carries.
Prepare(IReadOnlyDictionary<string, double>)¶
Resolves the draw order and running totals of a weight table. Pure: an unusable table (zero or negative total) is not rejected here but on the WeightedRoller.Roll() that tries to use it, so preparing a table early cannot move where the error surfaces.
Parameters
weights(IReadOnlyDictionary<string, double>) - Weight per key; keys are drawn in ordinal order whatever the dictionary's own.
Returns. The table to hand to WeightedRoller.Roll().
Roll(IReadOnlyDictionary<string, double>, IRng)¶
Prepares and rolls in one step. Prefer WeightedRoller.Prepare() plus WeightedRoller.Roll() when rolling the same table repeatedly.
Parameters
weights(IReadOnlyDictionary<string, double>) - Weight per key.rng(IRng) - The run's RNG.
Returns. The chosen key.
Roll(WeightedRoller.WeightTable, IRng)¶
Rolls a prepared table.
Parameters
table(WeightedRoller.WeightTable) - The prepared weights.rng(IRng) - The run's RNG.
Returns. The chosen key.
Exceptions
InvalidOperationException- The total is not a finite positive number.
Roll(WeightedRoller.WeightTable, double, IRng)¶
Rolls a prepared table that also carries a chance of choosing NOTHING — a layer that may be left out of an asset entirely.
Parameters
table(WeightedRoller.WeightTable) - The prepared weights.absentWeight(double) - Weight of the "nothing" outcome, competing with every key in the table. Zero for a table with no such outcome.rng(IRng) - The run's RNG.
Returns. The chosen key, or null for the absent outcome.
Exceptions
InvalidOperationException- The total is not a finite positive number.
Remarks
ONE DRAW, and at absentWeight zero this is the old arithmetic bit-for-bit. That is the whole design of the overload and it is not a nicety: the absent outcome is not a key in the table, so the key array and its ordinal order are untouched; total is 0 + table.Total, so r is drawn from exactly the same range; r < 0 is false and r -= 0 changes nothing, so the walk that follows sees the same number it always did. A cookbook that does not use optional layers therefore produces byte-identical output from the same seed, and every Set ever generated stays reproducible. AbsentOutcomeTests pins that against a recorded baseline rather than trusting this paragraph.
The absent outcome is drawn FIRST, before the keys. Which end it sits at is arbitrary, but it has to be fixed and stated somewhere, because it decides which outcome a given random number lands on.