From d01e576dccbd82c1b7c90ef2abde1c42f03149b7 Mon Sep 17 00:00:00 2001 From: Daniar Mannanov Date: Tue, 11 Aug 2026 13:23:45 +0300 Subject: [PATCH] Schedule features from mutable regions --- internal/world/decoration_region.go | 22 ++++++++++ internal/world/decoration_region_test.go | 55 +++++++++++++++++++++--- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/internal/world/decoration_region.go b/internal/world/decoration_region.go index 02d166d..cbb7e16 100644 --- a/internal/world/decoration_region.go +++ b/internal/world/decoration_region.go @@ -128,6 +128,28 @@ func (r *decorationRegion) sourceBiomes() []string { return names } +func (r *decorationRegion) scheduledFeatures(stage int) ([]worldgen.ScheduledFeature, error) { + set, err := worldgen.LoadFeatureSet() + if err != nil { + return nil, err + } + if err := r.ensureSourceNeighborhood(); err != nil { + return nil, err + } + return set.FeatureSchedule(possibleBiomeOrder(), r.sourceBiomes(), stage) +} + +func (r *decorationRegion) ensureSourceNeighborhood() error { + for cx := r.sourceX - 1; cx <= r.sourceX+1; cx++ { + for cz := r.sourceZ - 1; cz <= r.sourceZ+1; cz++ { + if _, ok := r.chunks[[2]int32{cx, cz}]; !ok { + return fmt.Errorf("world: source biome neighborhood missing (%d,%d)", cx, cz) + } + } + } + return nil +} + func (r *decorationRegion) placementContext(biomeAllows func(worldgen.FeaturePosition) bool) worldgen.PlacementContext { set, err := worldgen.LoadFeatureSet() if err != nil { diff --git a/internal/world/decoration_region_test.go b/internal/world/decoration_region_test.go index 6cff182..b6555ef 100644 --- a/internal/world/decoration_region_test.go +++ b/internal/world/decoration_region_test.go @@ -66,10 +66,14 @@ func TestDecorationRegionHeightmapsMatchPlacementSemantics(t *testing.T) { } func TestDecorationRegionSourceBiomesUseThreeByThreeChunks(t *testing.T) { - center := NewChunk(0, 0, BiomePlains) - east := NewChunk(1, 0, biomeIDByName("minecraft:desert")) - outside := NewChunk(2, 0, biomeIDByName("minecraft:forest")) - region, err := newDecorationRegion([]*Chunk{center, east, outside}) + var chunks []*Chunk + for cx := int32(-1); cx <= 1; cx++ { + for cz := int32(-1); cz <= 1; cz++ { + chunks = append(chunks, NewChunk(cx, cz, BiomePlains)) + } + } + chunks = append(chunks, NewChunk(2, 0, biomeIDByName("minecraft:forest"))) + region, err := newDecorationRegion(chunks) if err != nil { t.Fatal(err) } @@ -77,8 +81,49 @@ func TestDecorationRegionSourceBiomesUseThreeByThreeChunks(t *testing.T) { t.Fatal(err) } got := region.sourceBiomes() - want := []string{"minecraft:plains", "minecraft:desert"} + want := []string{"minecraft:plains"} if !reflect.DeepEqual(got, want) { t.Fatalf("source biomes = %v, want %v", got, want) } } + +func TestDecorationRegionSchedulesRealFeatureStage(t *testing.T) { + var chunks []*Chunk + for cx := int32(-1); cx <= 1; cx++ { + for cz := int32(-1); cz <= 1; cz++ { + chunks = append(chunks, NewChunk(cx, cz, BiomePlains)) + } + } + region, err := newDecorationRegion(chunks) + if err != nil { + t.Fatal(err) + } + if err := region.setSource(0, 0); err != nil { + t.Fatal(err) + } + features, err := region.scheduledFeatures(undergroundOresStage) + if err != nil { + t.Fatal(err) + } + if len(features) == 0 { + t.Fatal("plains underground stage produced no scheduled features") + } + for i, feature := range features { + if i > 0 && feature.Index <= features[i-1].Index { + t.Fatalf("feature %s index %d is not after index %d", feature.Name, feature.Index, features[i-1].Index) + } + } +} + +func TestDecorationRegionRequiresSourceBiomeNeighborhood(t *testing.T) { + region, err := newDecorationRegion([]*Chunk{NewChunk(0, 0, BiomePlains)}) + if err != nil { + t.Fatal(err) + } + if err := region.setSource(0, 0); err != nil { + t.Fatal(err) + } + if _, err := region.scheduledFeatures(undergroundOresStage); err == nil { + t.Fatal("missing source neighborhood succeeded") + } +}