using FluentAssertions; using Sand.ChunkPrototype; using Sand.Core; namespace Sand.ChunkPrototype.Tests; public sealed class PrototypeSparseSandAdapterTests { [Fact] public void StepDownMovesParticlesIntoEmptySpace() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(3, 2); var moves = adapter.StepDown(); moves.Should().Be(1); adapter.HasParticle(3, 2).Should().BeFalse(); adapter.HasParticle(3, 3).Should().BeTrue(); adapter.World.IsOccupied(3, 3).Should().BeTrue(); adapter.LastStepStats.MovementOnlyFastPathCount.Should().Be(1); adapter.LastStepStats.FullRuntimeStepCount.Should().Be(0); adapter.LastStepStats.MoveAttempts.Should().BeGreaterThan(0); adapter.LastStepStats.SuccessfulMoves.Should().Be(1); } [Fact] public void StepDownStopsAtBottomBoundary() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(3, 15); var moves = adapter.StepDown(); moves.Should().Be(0); adapter.HasParticle(3, 15).Should().BeTrue(); } [Fact] public void WaterFlowsSidewaysWhenBlockedBelow() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(8, 8, PrototypeParticleType.Water); adapter.AddParticle(8, 9, PrototypeParticleType.Wall); adapter.AddParticle(7, 9, PrototypeParticleType.Wall); adapter.AddParticle(9, 9, PrototypeParticleType.Wall); var moves = adapter.Step(); moves.Should().Be(1); (adapter.GetParticleTypeAt(7, 8) == PrototypeParticleType.Water || adapter.GetParticleTypeAt(9, 8) == PrototypeParticleType.Water).Should().BeTrue(); } [Fact] public void SteamRisesWhenSpaceAboveIsEmpty() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(8, 8, PrototypeParticleType.Steam); var moves = adapter.Step(); moves.Should().Be(1); adapter.GetParticleTypeAt(8, 7).Should().Be(PrototypeParticleType.Steam); } [Fact] public void SteamCrossingHorizontalChunkBoundaryDoesNotPauseBehindUpperChunkGas() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(2, 3, PrototypeParticleType.Steam); adapter.AddParticle(2, 4, PrototypeParticleType.Steam); adapter.AddParticle(1, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(1, 4, PrototypeParticleType.Wall); adapter.AddParticle(3, 4, PrototypeParticleType.Wall); var moves = adapter.Step(); moves.Should().Be(2); adapter.GetParticleTypeAt(2, 2).Should().Be(PrototypeParticleType.Steam); adapter.GetParticleTypeAt(2, 3).Should().Be(PrototypeParticleType.Steam); adapter.GetParticleTypeAt(2, 4).Should().Be(PrototypeParticleType.Empty); } [Fact] public void DenseSteamColumnAcrossHorizontalChunkBoundaryDoesNotLeaveGapRow() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(2, 3, PrototypeParticleType.Steam); adapter.AddParticle(2, 4, PrototypeParticleType.Steam); adapter.AddParticle(2, 5, PrototypeParticleType.Steam); adapter.AddParticle(1, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(1, 4, PrototypeParticleType.Wall); adapter.AddParticle(3, 4, PrototypeParticleType.Wall); adapter.AddParticle(1, 5, PrototypeParticleType.Wall); adapter.AddParticle(3, 5, PrototypeParticleType.Wall); var moves = adapter.Step(); moves.Should().Be(3); adapter.GetParticleTypeAt(2, 2).Should().Be(PrototypeParticleType.Steam); adapter.GetParticleTypeAt(2, 3).Should().Be(PrototypeParticleType.Steam); adapter.GetParticleTypeAt(2, 4).Should().Be(PrototypeParticleType.Steam); } [Fact] public void GasSeamFrameClearsOldBorderPixelAfterColumnCompaction() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(2, 3, PrototypeParticleType.Steam); adapter.AddParticle(2, 4, PrototypeParticleType.Steam); adapter.AddParticle(2, 5, PrototypeParticleType.Steam); adapter.AddParticle(1, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(1, 4, PrototypeParticleType.Wall); adapter.AddParticle(3, 4, PrototypeParticleType.Wall); adapter.AddParticle(1, 5, PrototypeParticleType.Wall); adapter.AddParticle(3, 5, PrototypeParticleType.Wall); adapter.Step(); var frame = adapter.BuildRgbaFrame().ToArray(); var clearedIndex = ((5 * 8) + 2) * 4; frame[clearedIndex].Should().Be(0); frame[clearedIndex + 1].Should().Be(0); frame[clearedIndex + 2].Should().Be(0); } [Fact] public void GasRenderFillsChunkRowSeamWhenCloudExistsAboveAndBelow() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(2, 2, PrototypeParticleType.Steam); adapter.AddParticle(2, 4, PrototypeParticleType.Steam); adapter.AddParticle(1, 3, PrototypeParticleType.Steam); adapter.AddParticle(3, 3, PrototypeParticleType.Steam); var frame = adapter.BuildRgbaFrame().ToArray(); var seamIndex = ((3 * 8) + 2) * 4; frame[seamIndex].Should().Be(182); frame[seamIndex + 1].Should().Be(196); frame[seamIndex + 2].Should().Be(214); } [Fact] public void WallRemainsStaticDuringStep() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(8, 8, PrototypeParticleType.Wall); var moves = adapter.Step(); moves.Should().Be(0); adapter.GetParticleTypeAt(8, 8).Should().Be(PrototypeParticleType.Wall); } [Fact] public void StepDownCrossesChunkBoundaryAndKeepsCountsConsistent() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(3, 3); adapter.World.ClearDirtyChunks(); var moves = adapter.StepDown(); moves.Should().Be(1); adapter.HasParticle(3, 4).Should().BeTrue(); adapter.World.GetChunkOccupancyCount(0, 0).Should().Be(0); adapter.World.GetChunkOccupancyCount(0, 1).Should().Be(1); adapter.World.DirtyChunks.Should().BeEquivalentTo([(0, 0), (0, 1)]); adapter.LastStepStats.SuccessfulMoves.Should().Be(1); adapter.LastStepStats.SwapAttempts.Should().Be(0); adapter.LastStepStats.MoveAttempts.Should().BeGreaterThan(0); } [Fact] public void TrimResidencyUnloadsDistantEmptyChunksAfterMovement() { var adapter = new PrototypeSparseSandAdapter(64, 64, new ChunkResidencyConfig(4, 4, Capacity: 64)); adapter.AddParticle(3, 3); adapter.AddParticle(40, 3); adapter.RemoveParticle(40, 3); var unloaded = adapter.TrimResidency(marginChunks: 0); unloaded.Should().BeGreaterThanOrEqualTo(1); adapter.World.IsChunkLoaded(10, 0).Should().BeFalse(); adapter.World.IsChunkLoaded(0, 0).Should().BeTrue(); } [Fact] public void BuildRgbaFrameIncludesDistinctColorsForParticleTypes() { var adapter = new PrototypeSparseSandAdapter(8, 8); adapter.AddParticle(1, 1, PrototypeParticleType.Sand); adapter.AddParticle(2, 1, PrototypeParticleType.Water); adapter.AddParticle(3, 1, PrototypeParticleType.Steam); adapter.AddParticle(4, 1, PrototypeParticleType.Wall); var frame = adapter.BuildRgbaFrame().ToArray(); frame[((1 * 8) + 1) * 4].Should().Be(214); frame[((1 * 8) + 2) * 4].Should().Be(72); frame[((1 * 8) + 3) * 4].Should().Be(182); frame[((1 * 8) + 4) * 4].Should().Be(96); } [Fact] public void CustomParticleProfilePreservesColorAndTypeId() { var adapter = new PrototypeSparseSandAdapter(8, 8); var particle = new PrototypeParticle(99, "custom_water", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 12, 34, 56, 1.2f, 0.4f, 0.1f, 0.2f); adapter.AddParticle(2, 3, particle).Should().BeTrue(); adapter.GetTypeIdAt(2, 3).Should().Be(99); var frame = adapter.BuildRgbaFrame().ToArray(); var index = ((3 * 8) + 2) * 4; frame[index].Should().Be(12); frame[index + 1].Should().Be(34); frame[index + 2].Should().Be(56); } [Fact] public void WindBrushPushesGasSidewaysWhenUpwardPathIsBlocked() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(8, 8, PrototypeParticleType.Steam); adapter.AddParticle(8, 7, PrototypeParticleType.Wall); adapter.AddParticle(7, 7, PrototypeParticleType.Wall); adapter.AddParticle(9, 7, PrototypeParticleType.Wall); adapter.ApplyWindBrush(8, 8, 3, 1f, 0f); adapter.ApplyWindBrush(8, 8, 3, 1f, 0f); var moves = adapter.Step(); moves.Should().Be(1); adapter.GetParticleTypeAt(9, 8).Should().Be(PrototypeParticleType.Steam); } [Fact] public void SolidContinuesSettlingAcrossIdleStepsWithoutExternalWorldChanges() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(8, 6, PrototypeParticleType.Sand); adapter.AddParticle(8, 7, PrototypeParticleType.Wall); var moved = false; for (var i = 0; i < 64; i++) { if (adapter.Step() > 0) { moved = true; break; } } moved.Should().BeTrue(); adapter.HasParticle(8, 6).Should().BeFalse(); } [Fact] public void WaterKeepsSurfaceDirectionInsteadOfImmediateBacktracking() { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(8, 8, PrototypeParticleType.Water); adapter.AddParticle(7, 9, PrototypeParticleType.Wall); adapter.AddParticle(8, 9, PrototypeParticleType.Wall); adapter.AddParticle(9, 9, PrototypeParticleType.Wall); adapter.Step().Should().Be(1); for (var i = 0; i < 4; i++) { adapter.Step(); } adapter.GetParticleTypeAt(8, 8).Should().Be(PrototypeParticleType.Empty); } [Fact] public void WaterHydratesSandIntoWetSand() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var wetSand = new PrototypeParticle(101, "wet_sand", PrototypeParticleType.Sand, ParticleKind.Solid, ParticleBehaviorKind.None, 120, 96, 64, 1.6f, 0.45f, 0.24f, 0.18f); var sand = new PrototypeParticle(100, "sand", PrototypeParticleType.Sand, ParticleKind.Solid, ParticleBehaviorKind.None, 214, 188, 96, 1.4f, 0.65f, 0.18f, 0.08f, HydrateTargetTypeId: 101); var water = new PrototypeParticle(102, "water", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 72, 132, 232, 1.0f, 0.55f, 0.04f, 0.12f, Flags: PrototypeParticleFlags.WaterLike); adapter.RegisterParticleProfile(wetSand); adapter.AddParticle(2, 2, sand); adapter.AddParticle(1, 2, water); adapter.AddParticle(1, 3, PrototypeParticleType.Wall); adapter.AddParticle(2, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(0, 2, PrototypeParticleType.Wall); adapter.AddParticle(0, 3, PrototypeParticleType.Wall); adapter.Step(); adapter.GetTypeIdAt(2, 2).Should().Be(101); adapter.GetTypeIdAt(1, 2).Should().Be(0); } [Fact] public void FullRuntimeParticleIncrementsFullRuntimeCounter() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var runtimeParticle = new PrototypeParticle(150, "runtime_sand", PrototypeParticleType.Sand, ParticleKind.Solid, ParticleBehaviorKind.None, 180, 150, 90, 1.2f, 0.4f, 0.15f, 0.05f, DefaultLifetime: 20f); adapter.RegisterParticleProfile(runtimeParticle); adapter.AddParticle(3, 2, runtimeParticle); adapter.Step(); adapter.LastStepStats.FullRuntimeStepCount.Should().Be(1); adapter.LastStepStats.MovementOnlyFastPathCount.Should().Be(0); } [Fact] public void StalledMovableCounterIncrementsWhenOpenPathExistsButSolidDoesNotSlip() { var foundStall = false; for (var x = 2; x < 14 && !foundStall; x++) { var adapter = new PrototypeSparseSandAdapter(16, 16, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(x, 6, PrototypeParticleType.Sand); adapter.AddParticle(x, 7, PrototypeParticleType.Wall); adapter.Step(); if (adapter.LastStepStats.StalledMovableCells > 0) { foundStall = true; } } foundStall.Should().BeTrue(); } [Fact] public void LavaAndWaterReactIntoStoneAndSteam() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var stone = new PrototypeParticle(201, "stone", PrototypeParticleType.Sand, ParticleKind.Solid, ParticleBehaviorKind.None, 96, 96, 96, 1.8f, 0.25f, 0.5f, 0.4f, IsStatic: true); var steam = new PrototypeParticle(202, "steam", PrototypeParticleType.Steam, ParticleKind.Gas, ParticleBehaviorKind.None, 182, 196, 214, 0.2f, 0.7f, 0.01f, 0.03f, SolidifyTypeId: 203, PressureThreshold: 1.2f, InitialTemperature: 110f); var water = new PrototypeParticle(203, "water", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 72, 132, 232, 1.0f, 0.55f, 0.04f, 0.12f, Flags: PrototypeParticleFlags.WaterLike, EvaporateTypeId: 202); var lava = new PrototypeParticle(204, "lava", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 255, 96, 24, 2f, 0.35f, 0.18f, 0.45f, Flags: PrototypeParticleFlags.HotSource, IsMolten: true, SolidifyTypeId: 201); adapter.RegisterParticleProfile(stone); adapter.RegisterParticleProfile(steam); adapter.AddParticle(2, 2, lava); adapter.AddParticle(3, 2, water); adapter.AddParticle(1, 2, PrototypeParticleType.Wall); adapter.AddParticle(4, 2, PrototypeParticleType.Wall); adapter.AddParticle(2, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(4, 3, PrototypeParticleType.Wall); adapter.Step(); adapter.GetTypeIdAt(2, 2).Should().Be(201); adapter.GetTypeIdAt(3, 2).Should().Be(202); } [Fact] public void HotNeighborEvaporatesWaterIntoSteam() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var steam = new PrototypeParticle(301, "steam", PrototypeParticleType.Steam, ParticleKind.Gas, ParticleBehaviorKind.None, 182, 196, 214, 0.2f, 0.7f, 0.01f, 0.03f, InitialTemperature: 110f); var water = new PrototypeParticle(302, "water", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 72, 132, 232, 1.0f, 0.55f, 0.04f, 0.12f, Flags: PrototypeParticleFlags.WaterLike, EvaporateTypeId: 301); var plasma = new PrototypeParticle(303, "plasma", PrototypeParticleType.Wall, ParticleKind.Solid, ParticleBehaviorKind.Plasma, 255, 64, 180, 10f, 0f, 1f, 1f, IsStatic: true, Flags: PrototypeParticleFlags.FireLike | PrototypeParticleFlags.HotSource); adapter.RegisterParticleProfile(steam); adapter.AddParticle(2, 2, water); adapter.AddParticle(3, 2, plasma); adapter.AddParticle(1, 2, PrototypeParticleType.Wall); adapter.AddParticle(2, 3, PrototypeParticleType.Wall); adapter.AddParticle(1, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.Step(); (adapter.GetTypeIdAt(2, 2) == 301 || adapter.GetTypeIdAt(2, 1) == 301).Should().BeTrue(); } [Fact] public void PressureCanBreakStaticParticleIntoConfiguredTarget() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var rubble = new PrototypeParticle(401, "rubble", PrototypeParticleType.Sand, ParticleKind.Solid, ParticleBehaviorKind.None, 160, 140, 120, 1.2f, 0.4f, 0.22f, 0.2f); var brittle = new PrototypeParticle(402, "brittle", PrototypeParticleType.Wall, ParticleKind.Solid, ParticleBehaviorKind.None, 210, 210, 210, 5f, 0f, 1f, 1f, IsStatic: true, BrokenTypeId: 401, PressureThreshold: 0.35f, PressureThresholdDuration: 1); adapter.RegisterParticleProfile(rubble); adapter.AddParticle(4, 4, brittle); adapter.ApplyAirBrush(4, 4, 2, 0f, 12f); adapter.ApplyAirBrush(4, 4, 2, 0f, 12f); adapter.Step(); adapter.GetTypeIdAt(4, 4).Should().Be(401); } [Fact] public void PressurizedSteamCanCondenseIntoWater() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var water = new PrototypeParticle(501, "water", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 72, 132, 232, 1.0f, 0.55f, 0.04f, 0.12f, Flags: PrototypeParticleFlags.WaterLike); var steam = new PrototypeParticle(502, "steam", PrototypeParticleType.Steam, ParticleKind.Gas, ParticleBehaviorKind.None, 182, 196, 214, 0.2f, 0.7f, 0.01f, 0.03f, SolidifyTypeId: 501, PressureThreshold: 0.6f, InitialTemperature: 110f); adapter.RegisterParticleProfile(water); adapter.AddParticle(4, 4, steam); adapter.AddParticle(4, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(5, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 4, PrototypeParticleType.Wall); adapter.AddParticle(5, 4, PrototypeParticleType.Wall); adapter.AddParticle(4, 5, PrototypeParticleType.Wall); adapter.ApplyAirBrush(4, 4, 1, 0f, 4f); adapter.Step(); adapter.GetTypeIdAt(4, 4).Should().Be(501); } [Fact] public void BlockedGasRiseDoesNotKeepRetryingVerticalProbeWhenCeilingIsUnchanged() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(4, 4, PrototypeParticleType.Steam); adapter.AddParticle(4, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 3, PrototypeParticleType.Wall); adapter.AddParticle(5, 3, PrototypeParticleType.Wall); adapter.AddParticle(3, 4, PrototypeParticleType.Wall); adapter.AddParticle(5, 4, PrototypeParticleType.Wall); adapter.Step(); adapter.Step(); adapter.LastStepStats.VerticalMoveAttempts.Should().Be(0); } [Fact] public void StableHotGasCanSkipFullRuntimeOnSomeSteps() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var hotGas = new PrototypeParticle(550, "hot_gas", PrototypeParticleType.Steam, ParticleKind.Gas, ParticleBehaviorKind.None, 220, 180, 220, 0.25f, 0.45f, 0.02f, 0.03f, InitialTemperature: 1000f, Conductivity: 1f); adapter.RegisterParticleProfile(hotGas); adapter.AddParticle(2, 5, hotGas); adapter.AddParticle(3, 5, hotGas); adapter.AddParticle(2, 6, hotGas); adapter.AddParticle(3, 6, hotGas); var observedReducedRuntime = false; for (var i = 0; i < 4; i++) { adapter.Step(); if (adapter.LastStepStats.FullRuntimeGasCount < 4) { observedReducedRuntime = true; break; } } observedReducedRuntime.Should().BeTrue(); } [Fact] public void StableHotGasAdjacentToWallCanStillSkipFullRuntime() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var hotGas = new PrototypeParticle(551, "hot_wall_gas", PrototypeParticleType.Steam, ParticleKind.Gas, ParticleBehaviorKind.None, 220, 180, 220, 0.25f, 0.45f, 0.02f, 0.03f, InitialTemperature: 1000f, Conductivity: 1f); adapter.RegisterParticleProfile(hotGas); adapter.AddParticle(3, 5, hotGas); adapter.AddParticle(4, 5, hotGas); adapter.AddParticle(2, 5, PrototypeParticleType.Wall); adapter.AddParticle(5, 5, PrototypeParticleType.Wall); var observedReducedRuntime = false; for (var i = 0; i < 4; i++) { adapter.Step(); if (adapter.LastStepStats.FullRuntimeGasCount < 2) { observedReducedRuntime = true; break; } } observedReducedRuntime.Should().BeTrue(); } [Fact] public void AmbientSolidDoesNotInstantlyMeltJustBecauseItHasMeltTarget() { var adapter = new PrototypeSparseSandAdapter(8, 8, new ChunkResidencyConfig(4, 4, Capacity: 16)); var molten = new PrototypeParticle(601, "molten_test", PrototypeParticleType.Water, ParticleKind.Liquid, ParticleBehaviorKind.None, 255, 96, 24, 2f, 0.35f, 0.18f, 0.45f, IsMolten: true, InitialTemperature: 140f); var solid = new PrototypeParticle(602, "solid_test", PrototypeParticleType.Sand, ParticleKind.Solid, ParticleBehaviorKind.None, 140, 140, 140, 1.8f, 0.25f, 0.45f, 0.35f, MeltTypeId: 601, MeltTemperature: 900f, InitialTemperature: 22f); adapter.RegisterParticleProfile(molten); adapter.AddParticle(4, 4, solid); adapter.AddParticle(4, 5, PrototypeParticleType.Wall); adapter.Step(); adapter.GetTypeIdAt(4, 4).Should().Be(602); } [Fact] public void SteamContinuesRisingAcrossIdleStepsWithoutExternalWake() { var adapter = new PrototypeSparseSandAdapter(8, 12, new ChunkResidencyConfig(4, 4, Capacity: 16)); adapter.AddParticle(4, 9, PrototypeParticleType.Steam); for (var i = 0; i < 4; i++) { adapter.Step(); } adapter.ParticleEntries.Should().Contain(entry => entry.Value.MotionType == PrototypeParticleType.Steam && entry.Key.Y <= 6); } }