Carve terrain on demand

- eager terrain window covers the replayed object sources plus one chunk of footprint (radius 13 instead of 19 for overworld)
- content writers generate a chunk's missing terrain components before handing it out, so reads past the window stay identical
- getEagerInputRadius replaces isInputGenerationLazy
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 13:32:53 -04:00
1 parent 8d93431319
commit 808e133cac
8 files changed
+231 -48

No files matched your search

@@ -45,6 +45,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -188,6 +189,23 @@ public class MantleObjectComponentBoundaryRadiusTest {
MantleObjectComponent.calculateInputRadius(Integer.MAX_VALUE, true));
}
@Test
public void eagerInputRadiusCoversEverySourceChunkPlusOneChunkOfFootprint() {
assertEquals(129, MantleObjectComponent.calculateEagerInputRadius(103, false));
assertEquals(65, MantleObjectComponent.calculateEagerInputRadius(33, false));
assertEquals(113, MantleObjectComponent.calculateEagerInputRadius(33, true));
assertEquals(27, MantleObjectComponent.calculateEagerInputRadius(10, false));
assertEquals(1, MantleObjectComponent.calculateEagerInputRadius(0, false));
for (int radius = 0; radius < 300; radius++) {
for (boolean collisions : new boolean[]{false, true}) {
assertTrue(MantleObjectComponent.calculateEagerInputRadius(radius, collisions)
<= MantleObjectComponent.calculateInputRadius(radius, collisions));
}
}
assertEquals(Integer.MAX_VALUE,
MantleObjectComponent.calculateEagerInputRadius(Integer.MAX_VALUE, true));
}
@Test
public void proceduralTreeTransformsContributeToRadius() {
IrisObjectPlacement placement = new IrisObjectPlacement()
@@ -309,8 +309,8 @@ public class MatterGeneratorCarvePassRadiusTest {
}
@Override
public boolean isInputGenerationLazy() {
return lazyInputGeneration;
public int getEagerInputRadius(int inputRadius) {
return lazyInputGeneration ? 0 : inputRadius;
}
@Override
@@ -387,27 +387,94 @@ public class MatterGeneratorConcurrencyTest {
}
@Test
public void contentPhaseRequiresTerrainEvenWhenItsExplicitPrerequisiteIsMissing() {
public void contentPhaseGeneratesTerrainOfEveryChunkBeforeItIsReadOrWritten() {
GeneratorFixture fixture = new GeneratorFixture(true);
List<String> events = new ArrayList<>();
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
@Override
public MatterGenerationPhase getGenerationPhase() {
return MatterGenerationPhase.TERRAIN;
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
assertSame(fixture.mantle.getChunk(x, z), writer.acquireChunk(x, z));
events.add("terrain " + x + "," + z);
}
};
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
@Override
public MantleFlag[] getPrerequisiteFlags() {
return new MantleFlag[]{ReservedFlag.CARVED};
}
@Override
public int getInputRadius() {
return 48;
}
@Override
public int getEagerInputRadius(int inputRadius) {
return 0;
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
events.add("content " + x + "," + z);
assertSame(fixture.mantle.getChunk(x + 3, z), writer.acquireChunk(x + 3, z));
writer.acquireChunk(x + 3, z);
events.add("read " + (x + 3) + "," + z);
}
};
TestMatterGenerator generator = fixture.generator(List.of(
new MantlePass(List.of(terrain), 0, 0),
new MantlePass(List.of(content), 0, 0)));
assertThrows(IllegalStateException.class,
() -> generator.generateContentMatter(0, 0, false, fixture.context));
assertFalse(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
generator.generateTerrainMatter(0, 0, false, fixture.context);
assertEquals(List.of("terrain 0,0"), events);
generator.generateContentMatter(0, 0, false, fixture.context);
assertEquals(List.of("terrain 0,0", "content 0,0", "terrain 3,0", "read 3,0"), events);
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
assertTrue(fixture.mantle.getChunk(3, 0).isFlagged(ReservedFlag.CARVED));
assertFalse(fixture.mantle.getChunk(3, 0).isFlagged(ReservedFlag.OBJECT));
}
@Test
public void contentPhaseGeneratesMissingTerrainOfItsOwnChunk() {
GeneratorFixture fixture = new GeneratorFixture(true);
AtomicInteger terrainRuns = new AtomicInteger();
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
@Override
public MatterGenerationPhase getGenerationPhase() {
return MatterGenerationPhase.TERRAIN;
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
terrainRuns.incrementAndGet();
}
};
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
@Override
public MantleFlag[] getPrerequisiteFlags() {
return new MantleFlag[]{ReservedFlag.CARVED};
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
assertEquals(1, terrainRuns.get());
}
};
TestMatterGenerator generator = fixture.generator(List.of(
new MantlePass(List.of(terrain), 0, 0),
new MantlePass(List.of(content), 0, 0)));
generator.generateContentMatter(0, 0, false, fixture.context);
assertEquals(1, terrainRuns.get());
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(ReservedFlag.OBJECT));
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
}
@Test