Gameplay · guide
Authoring a loot table
An editable model over the definition, a flattened outline for a tree view, and a drop simulator that runs the shipped evaluator.
Edit this page on GitHubDocuments
What it is#
Three things a loot-table editor needs and one of them is the interesting one.
LootTableModel— the definition, edited in place, with every gesture as one operation and one-
Changed, plus aValidatethat says everything the content build would refuse. LootOutline— the tree flattened depth-first, with each weighted row's share of its table's pick.LootSimulator— ten thousand kills through the shippedLootEvaluator, reported as rates.
What it is for#
Balancing a table without shipping it. A designer changes a weight and sees what it did, in the same arithmetic the realm will run — which is doc 28's actual requirement: *"simulated in the editor with the real evaluator"*.
A simulator with its own arithmetic is a second set of odds, and the one a designer balances against is the one that is wrong. That is the whole argument for the evaluator being a library.
Using it#
Wrap the definition in a model, run the simulator after each edit, and draw the outline beside its results — the outline is what the table was *written* to do and the simulation is what it does, and a gap between them is a condition nobody accounted for.
⚠ A simulation starts from a fresh pity store. One that inherited a live player's bad luck would report a rate nobody else will ever see. It also means the simulator and a hand-written loop only agree if both are given a store: a pity row that drops consumes an extra draw from the stream.
⚠ EmptyEvents is the figure to look at first. A table whose weighted rows are all conditional
drops nothing on an ordinary kill, and the authored file looks perfectly reasonable.
⚠ GuaranteeHeld is a bug report, not a balance figure. A drought longer than the pity policy's
guarantee is a ramp that never starts or an evaluator that stopped honouring the policy.
⚠ A conditional row's share is true of no actual kill — it is computed as though every conditional
row were present — so the row carries a Conditional flag and a view must show it.
⚠ Moving a row is a real edit. The evaluator runs independent rows in authored order, so reordering changes what every recorded event id produces. It is not a sort.
Examples#
Editing, validating, and previewing after each gesture:
using System.Collections.Generic;using Vixen.Editor.Gameplay.Loot;using Vixen.Gameplay;using Vixen.Gameplay.Items;using Vixen.Gameplay.Loot;static class Authoring { public static LootSimulation Preview( LootLibrary loot, ItemLibrary items, LootTableModel model, DefId table ) { model.Edit(0, entry => entry.Weight = 4f); // Everything the content build would refuse, attributed to a row so it can be highlighted. IReadOnlyList<LootProblem> problems = model.Validate(); _ = problems; // The same evaluator the realm runs, over a run somebody else can reproduce. return LootSimulator.Run(loot, loot.Get(table), items, events: 10000, firstEventId: 1); }}Reading the result the way a table view would:
using System;using Vixen.Editor.Gameplay.Loot;static class Report { public static void Write(LootSimulation simulation, Action<string> line) { line($"{simulation.EmptyEvents} of {simulation.Events} kills dropped nothing"); foreach (var item in simulation.Items) { line($"{item.Name}: {item.RateOver(simulation.Events):P2} of kills, {item.PerEvent(simulation.Events):F2} each"); } if (simulation.Pity is { } pity && !pity.GuaranteeHeld) { line($"a drought of {pity.LongestDrought} exceeded the guarantee of {pity.Guarantee}"); } }}Drawing the tree beside it:
using System;using Vixen.Editor.Gameplay.Loot;using Vixen.Gameplay.Items;using Vixen.Gameplay.Loot;static class Tree { public static void Write(LootLibrary loot, LootTable table, ItemLibrary items, Action<string> line) { foreach (var row in LootOutline.Of(loot, table, items)) { var share = row.Chance > 0f ? $"{row.Chance:P1} chance" : $"{row.Share:P1} of the pick"; line($"{new string(' ', row.Depth * 2)}{row.Label} — {share}{(row.Conditional ? " (conditional)" : "")}"); } }}See also#
- Loot tables — the evaluator this previews, and the rules it enforces.
- Items — where the names in the report come from.
- Gameplay randomness — why a simulation is reproducible at all.