Skip to content

WeightedRoller

Namespace Nfty.Core.Generation in assembly Nfty.Ticket.

public static class WeightedRoller

Weighted selection with a stable, locale-independent draw order.

Methods

AbsentWeight(double, double)

public static double AbsentWeight(double percent, double variantTotal);

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)

public static bool AlwaysAbsent(double percent);

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>)

public static WeightedRoller.WeightTable Prepare(IReadOnlyDictionary<string, double> weights);

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)

public static string Roll(IReadOnlyDictionary<string, double> weights, IRng rng);

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)

public static string Roll(WeightedRoller.WeightTable table, IRng rng);

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)

public static string? Roll(WeightedRoller.WeightTable table, double absentWeight, IRng rng);

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.