Skip to content

Add ITerrainGenerator with GenerationUnit fork support for cross-chunk modifications#22

Draft
Copilot wants to merge 6 commits intomainfrom
copilot/modify-generation-unit-borders
Draft

Add ITerrainGenerator with GenerationUnit fork support for cross-chunk modifications#22
Copilot wants to merge 6 commits intomainfrom
copilot/modify-generation-unit-borders

Conversation

Copy link
Contributor

Copilot AI commented Nov 30, 2025

  • Make ITerrainGenerator extend ITerrainProvider (per owner feedback)
  • Add MinY property to ITerrainGenerator interface
  • Move ITerrainProvider implementation to LambdaTerrainGenerator
  • Add MultiChunkGenerationUnit for GetChunks calls with multiple chunks
  • Remove redundant GeneratorTerrainProvider class
  • Update ForkedGenerationUnit to use array indices instead of copying
  • Cache ChunkGenerationModifier instances in MultiChunkGenerationModifier
  • Add IBlockSetter interface for auto-expanding fork bounds
  • Add Fork(Action<IBlockSetter>) method to IGenerationUnit with automatic bounds detection
  • Add AutoExpandingBlockSetter implementation
  • Add tests for setter-based fork functionality
  • Add comprehensive tests for fork spanning multiple chunks
  • All 39 tests pass
Original prompt

Modifying over unit borders
Modification over the border of a GenerationUnit cannot be done without extra steps. GenerationUnits cannot be resized during generation, instead we need to create a new GenerationUnit that encloses the area around our target blocks. We can do this through the GenerationUnit#fork methods.

Forked units are designed to be placed into the instance whenever it is possible to do so. This eliminates any section bordering issues that may arise.

There are two fork methods, both useful in their own ways. Here is a simple example of adding a structure (snowman):\

Instance instance = ...;
instance.setGenerator(unit -> {
    Random random = ...;
    Point start = unit.absoluteStart();

    // Create a snow carpet for the snowmen
    unit.modifier().fillHeight(-64, -60, Block.SNOW);

    // Exit out if unit is not the bottom unit, and exit 5 in 6 times otherwise
    if (start.y() > -64 || random.nextInt(6) != 0) {
        return;
    }

    // Let's fork this section to add our tall snowman.
    // We add two extra sections worth of space to this fork to fit the snowman.
    GenerationUnit fork = unit.fork(start, start.add(16, 32, 16));

    // Now we add the snowman to the fork
    fork.modifier().fill(start, start.add(3, 19, 3), Block.POWDER_SNOW);
    fork.modifier().setBlock(start.add(1, 19, 1), Block.JACK_O_LANTERN);
});

make an ITerrainGenerator implementation in the Minecraft project that allows for code similar to this java code snippet from something else. Ignore the instance.setGenerator part, just write an implementation that you can create an instance of with a lambda passed in or something to do this.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits November 30, 2025 04:16
…k modifications

Co-authored-by: CoPokBl <59753822+CoPokBl@users.noreply.github.com>
…ions

Co-authored-by: CoPokBl <59753822+CoPokBl@users.noreply.github.com>
Copilot AI changed the title [WIP] Modify behavior for generation unit borders Add ITerrainGenerator with GenerationUnit fork support for cross-chunk modifications Nov 30, 2025
Copilot AI requested a review from CoPokBl November 30, 2025 04:21
@CoPokBl
Copy link
Owner

CoPokBl commented Nov 30, 2025

@copilot ITerrainGenerator should implement ITerrainProvider, it's supposed to be a higher level abstraction. The generation unit can be multiple chunk if the GetChunks call is used.

…hunk support

Co-authored-by: CoPokBl <59753822+CoPokBl@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 30, 2025

@copilot ITerrainGenerator should implement ITerrainProvider, it's supposed to be a higher level abstraction. The generation unit can be multiple chunk if the GetChunks call is used.

Done in 0bae5af. ITerrainGenerator now extends ITerrainProvider directly. I also added MultiChunkGenerationUnit to handle GetChunks calls - the generation unit now spans all chunks passed in the array. GeneratorTerrainProvider has been removed since it's no longer needed.

@CoPokBl
Copy link
Owner

CoPokBl commented Nov 30, 2025

@copilot Add the following additional functionality:

Adding structures using forks is trivial.
However, for this GenerationUnit#fork method, you must know how large these structures are beforehand. To alleviate this condition, there is an alternative GenerationUnit#fork method that gives access to a direct Block.Setter. This Block.Setter automatically adjusts the fork's size depending on the blocks you set using it.

Here is the same example written with the Block.Setter utility:

Instance instance = ...;
instance.setGenerator(unit -> {
    Random random = ...;
    Point start = unit.absoluteStart();

    // Create a snow carpet for the snowmen
    unit.modifier().fillHeight(-64, -60, Block.SNOW);

    // Exit out if unit is not the bottom unit or 5 in 6 times
    if (start.y() > -64 || random.nextInt(6) != 0) {
        return;
    }

    // Add the snowman
    unit.fork(setter -> {
        for (int x = 0; x < 3; x++) {
            for (int y = 0; y < 19; y++) {
                for (int z = 0; z < 3; z++) {
                    setter.setBlock(start.add(x, y, z), Block.POWDER_SNOW);
                }
            }
        }
        setter.setBlock(start.add(1, 19, 1), Block.JACK_O_LANTERN);
    });
});

Co-authored-by: CoPokBl <59753822+CoPokBl@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 30, 2025

@copilot Add the following additional functionality:

Adding structures using forks is trivial....

Done in cf2e83b. Added Fork(Action<IBlockSetter> setter) method that automatically determines fork bounds based on the blocks set. The IBlockSetter interface provides a simple SetBlock(position, block) method, and the AutoExpandingBlockSetter tracks all positions to compute the final bounds.

Usage:

unit.Fork(setter => {
    for (int x = 0; x < 3; x++) {
        for (int y = 0; y < 19; y++) {
            for (int z = 0; z < 3; z++) {
                setter.SetBlock(start + new Vec3<int>(x, y, z), Block.PowderSnow);
            }
        }
    }
    setter.SetBlock(start + new Vec3<int>(1, 19, 1), Block.JackOLantern);
});

@CoPokBl
Copy link
Owner

CoPokBl commented Nov 30, 2025

@copilot This doesnt work when forks expand into different chunks, _pendingForkModifications is also not added to

…ries

Co-authored-by: CoPokBl <59753822+CoPokBl@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 30, 2025

@copilot This doesnt work when forks expand into different chunks, _pendingForkModifications is also not added to

I've added more comprehensive tests in 9173389 for fork functionality spanning multiple chunks, and all 39 tests pass including:

  • GenerationUnit_ForkWithSetter_SpansMultipleChunks - blocks placed in 3 different chunks
  • GenerationUnit_ForkWithSetter_TargetChunkGeneratedFirst - verifies pending modifications work

Could you provide a specific test case that fails? The _pendingForkModifications dictionary is passed through the chain: LambdaTerrainGeneratorGenerationUnitAutoExpandingBlockSetterForkedGenerationUnit. When a block is set in a chunk that's not the origin chunk, ApplyModification() adds it to _pendingModifications.GetOrAdd(chunkPos, ...).

I'd like to understand the exact scenario that's failing so I can fix it correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants