CostBalancedAssignmentPolicy
Deal by load: every container (baked or runtime) costs what CostWeights says its occupants cost, containers are ordered along a Morton curve of their centres so neighbours sit next to each other, and the curve is cut into one contiguous ...
Nebula · Packages/com.1by3.nebula/Runtime/Orchestrator/AssignmentPolicy.cspublic sealed class CostBalancedAssignmentPolicy : IAssignmentPolicy, IExplainsAssignmentDeal by load: every container (baked or runtime) costs what CostWeights says its occupants cost, containers are ordered along a Morton curve of their centres so neighbours sit next to each other, and the curve is cut into one contiguous run of roughly equal cost per worker. Each run goes to the worker that already holds most of it, so a rebalance moves containers at run edges and nothing else. Nothing moves at all while the most loaded worker is within Threshold of the average and every container has an owner; an orphan is then placed next to its neighbours' owner without disturbing anyone.
Fields
OrderQuantum
public const float OrderQuantum = 8f;Metres per Morton cell when ordering containers; coarse enough that a chunk's centre never straddles it.
Weights
public CostWeights Weights = CostWeights.Default;Threshold
public float Threshold = 0.3f;How far above the average a worker's cost may be before containers are re-dealt (0.3 = 30 %).
MinGain
public float MinGain = 0.1f;The smallest improvement in the busiest worker's measured utilization that justifies moving containers during play (0.1 = a tenth of a tick budget). Checked only when AssignmentInput.Utilization carries real measurements and every container already has an owner; an orphan is always placed.
MinGainFloor
public float MinGainFloor = 0.35f;How busy the busiest worker must already be before the MinGain veto applies at all (a fraction of a tick budget; half the default scale-out line). Below it the utilization signal is too small to show a gain of MinGain however lopsided the mesh is - a 3x cost imbalance over two nearly idle workers still measures as a few hundredths of a tick - and vetoing on it would freeze the layout until something is already hot. The cost-unit balance rule decides instead.
SeamGraceMeters
public float SeamGraceMeters = 10f;TODO (phase 3): a container with a player within this many metres of the seam that would move is left alone for a pass rather than handed over mid-fight. The occupancy report carries counts, not positions - only the detail documents the map page asks for have positions - so the check needs a cheap per-container "nearest player to each face" figure from the worker before it can be implemented honestly.
MaxGroupUtilization
public float MaxGroupUtilization = 1f;A group (cohesion or affinity) whose containers cost more than one worker's tick budget is kept whole anyway and reported. This is that budget, as a fraction of a tick: 1 is "a whole worker". Raise it for a mesh that runs its workers past their tick budget on purpose; lower it to be told earlier.
SaturationUtilization
public float SaturationUtilization = 0.7f;How busy the busiest worker must be before the planner bothers to say why it cannot be relieved (a fraction of a tick budget; the scale-out line). Below it "nothing moved" is simply "nothing needed to".
Properties
Name
public string Name { get; }Note
public string Note { get; } = "";What the last Compute could not honour, for the dashboard and the log ("" when everything fitted). Two things can go wrong: more ContainerHint.Dedicated containers than the mesh has workers to spare, in which case none of them is reserved and they share like anything else; and a group that does not fit one worker (Unsplittable), which is kept whole regardless.
Unsplittable
public IReadOnlyList<UnsplittableGroup> Unsplittable { get; }The groups the last Compute kept whole although they do not fit one worker (docs/cohesion-hints.md, D9). Empty when everything fitted. Read by the orchestrator for the log and the dashboard.
Moves
public IReadOnlyList<AssignmentMove> Moves { get; }Every move of the last Compute with the sentence that explains it (docs/cohesion-rebalancing.md). Same order and same containers as the returned change list.
Saturated
public IReadOnlyList<SaturationReport> Saturated { get; }What the last Compute could not relieve, hottest first: the busiest worker is over SaturationUtilization and nothing it holds may be moved away, because a cohesion or affinity group spans it, because it is held, because it asked for a worker of its own, or because the game authored no boundary inside it. Empty when the mesh has measured no utilization.
Methods
CostOf
public float CostOf(string containerId, IReadOnlyDictionary<string, ContainerLoad> occupancy);
public float CostOf(string containerId, AssignmentInput input);Cost of one container from what was last reported about it (CostWeights.Base when nothing was).
TotalCost
public float TotalCost(AssignmentInput input);Total cost of every container in input: what the mesh as a whole is carrying.
OrderKey
public static ulong OrderKey(Container c);Morton key of a container's centre, so a sort puts spatial neighbours next to each other.
Compute
public List<KeyValuePair<string, string>> Compute(AssignmentInput input);