Fate
Decide your players' Fate!
Weighted random drops for when randomness is just a little bit too random.
Weighted random drops so you can craft the randomness the way you need it to behave!
What is Fate.
A system to allow you to shape how random drops work
Weighted random selection can end up getting super complicated. Basic weighted rolls are fine, but sometimes you need one item to never drop twice in a row, and another to be guaranteed at least once in every ten pulls, maybe a rare item that gets progressively more likely the longer someone goes without seeing it, or items to be dropped from a shuffle-style emptying bag, and suddenly you’re maintaining a pile of counters and flags and special cases that make changes awkward and difficult to reason about, and you’re never exactly sure if the drop chance is right.
Fate handles all of that. Basic drops are simple, while you get a ton of options to deal with more complex cases.
Also, don’t just think of this as a loot system. Fate is useful for any weighted random selection: which enemies spawn on a map, which events fire during a run, which NPC shows up at the shop, whatever your game needs to decide by weighted chance.
How to use it.
// Create some drop entries
var _common = new FateValueEntry("COMMON").SetWeight(94);
var _rare = new FateValueEntry("RARE").SetWeight(6);
// Add them to a loot table
var loot_table = new FateTable([_common, _rare]);
// When you need a drop, roll for it
var _roll = FateRollValues(loot_table, 1);
// Check that the roll was valid
if (_roll.IsOk()) {
// And grab the drop from the roll
var _drop = _roll.GetFirstDrop();
}
That’s it for the simple case. Build a table, roll it, check the result. But there’s quite a lot more available when you need it.
Features.
Entries can be marked as guaranteed (always included in a roll regardless of weight), unique (never selected more than once per roll), or weighted normally. A FateTableEntry can wrap another table as an entry, so outcomes can branch recursively through multi-stage generation. A FateCreatorEntry makes it easy to create an actual game instance or struct when it’s selected, so a roll can directly produce a live instance or struct rather than just a value you then have to act on.
Policies.
The policy system is where Fate starts to spread its wings in terms of giving you options. Policies modify selection behavior at roll time without you changing your entries or your roll calls. The built-in ones cover the things that come up constantly in games: pity (rare entries become progressively more likely the longer someone goes without hitting them), rate-up (featured entries get a weight boost for a period), duplicate protection (recently selected entries are penalized or excluded from the next roll), and batch guarantees (at least one target entry in every N rolls). There’s also EnableStandardGachaRules if you want the full set in one call, which sets up pity, rate-up and featured handling the way a gacha player would usually expect them to work.
Inspection.
The probability tools let you inspect what Fate would actually do before committing to it. Preview evaluates likely outcomes without running side effects, GetEntryProbability gives you the current effective chance for a specific entry under active policies, and GetValueProbability does the same by value rather than entry reference. These are useful for showing probability info in UI or for balance work without having to do the math yourself.
Quality assurance.
For QA there’s a simulation suite that runs a table thousands of times and gives you a report with actual observed rates per entry, a deterministic result hash (so you can get the rate you want, hash it, and then compare future changes against that hash to make sure you didn’t accidentally introduce changes), and assertion helpers so you can write checks like “this entry should appear between 5% and 7% of the time” and get a clear pass/fail. If your drop rates drift after a change, you’ll know.
