feat: weight vertical distance in chunk-load priority

Adds Configuration.VerticalPriorityWeight (default 4). The priority
formula in ChunkStreamer.RecomputeTargetSet now uses
dx^2 + dz^2 + dy^2 * VerticalPriorityWeight, so horizontal chunks at
the player's Y are dispatched before equally-distant chunks above or
below. Fixes janky loading where deep-underground chunks were
competing with surface chunks for early dispatch slots.
This commit is contained in:
2026-06-01 13:41:55 +02:00
parent 4fe552b8bd
commit 2c95c7831a
3 changed files with 40 additions and 3 deletions

View File

@@ -10,6 +10,11 @@ public sealed class Configuration
public int MaxMeshUploadsPerFrame { get; set; } = 4; public int MaxMeshUploadsPerFrame { get; set; } = 4;
public int WorkerThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2); public int WorkerThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2);
// Multiplier applied to (dy)^2 in chunk-load priority. >1 prioritizes
// chunks at the player's Y over equally-distant chunks above/below, so the
// visible surface fills in before far-underground / overhead chunks.
public int VerticalPriorityWeight { get; set; } = 4;
public float MoveSpeed { get; set; } = 8f; public float MoveSpeed { get; set; } = 8f;
public float MouseSensitivity { get; set; } = 0.15f; public float MouseSensitivity { get; set; } = 0.15f;
public float Fov { get; set; } = 60f; public float Fov { get; set; } = 60f;

View File

@@ -351,4 +351,33 @@ public class ChunkStreamerTests
streamer.Tick(new Vector3i(0, 0, 0)); streamer.Tick(new Vector3i(0, 0, 0));
Assert.Equal(1, generateCalls); Assert.Equal(1, generateCalls);
} }
[Fact]
public void Tick_VerticalPriorityWeight_PrefersHorizontalAtSameDistance()
{
var cfg = new Configuration
{
RenderDistanceHorizontal = 1,
RenderDistanceVertical = 1,
MaxGenerateJobsInFlight = 100,
MaxMeshUploadsPerFrame = 5, // player's chunk + 4 horizontal cardinals
VerticalPriorityWeight = 4,
};
var (streamer, world, _) = CreateStreamer(cfg);
// Player chunk at Y=2 so vertical neighbors Y=1 and Y=3 are valid.
var playerPos = new Vector3i(0, 2, 0);
streamer.Tick(playerPos);
// Drain budget = 5 = priority 0 (player) + 4 priority-1 (horizontal
// cardinals). With VerticalPriorityWeight=4, vertical cardinals
// (Y=1, Y=3) have priority 4 — they should NOT be among the first 5.
Assert.NotNull(world.GetChunk(playerPos));
Assert.NotNull(world.GetChunk(new Vector3i( 1, 2, 0)));
Assert.NotNull(world.GetChunk(new Vector3i(-1, 2, 0)));
Assert.NotNull(world.GetChunk(new Vector3i( 0, 2, 1)));
Assert.NotNull(world.GetChunk(new Vector3i( 0, 2, -1)));
Assert.Null(world.GetChunk(new Vector3i(0, 3, 0)));
Assert.Null(world.GetChunk(new Vector3i(0, 1, 0)));
}
} }

View File

@@ -122,9 +122,12 @@ public sealed class ChunkStreamer : IDisposable
if (this._failed.Contains(pos)) continue; if (this._failed.Contains(pos)) continue;
if (this._tracked.Add(pos)) if (this._tracked.Add(pos))
{ {
int priority = (pos.X - p.X) * (pos.X - p.X) int dx = pos.X - p.X;
+ (pos.Y - p.Y) * (pos.Y - p.Y) int dy = pos.Y - p.Y;
+ (pos.Z - p.Z) * (pos.Z - p.Z); int dz = pos.Z - p.Z;
int priority = dx * dx
+ dz * dz
+ dy * dy * this._cfg.VerticalPriorityWeight;
this._pendingLoad.Enqueue(pos, priority); this._pendingLoad.Enqueue(pos, priority);
} }
} }