Schedule source chunk features globally

This commit is contained in:
Daniar Mannanov 2026-08-11 12:47:59 +03:00
parent 11440f556c
commit ea66091f85
4 changed files with 125 additions and 0 deletions

View file

@ -28,6 +28,14 @@ type IndexedFeature struct {
Index int
}
// ScheduledFeature is one feature selected for a decoration stage. Index is
// the stable FeatureSorter index within that stage and must be used when
// deriving the feature random seed; it is not the index in any one biome.
type ScheduledFeature struct {
Name string
Index int
}
type ConfiguredFeature struct {
Type string
Config json.RawMessage
@ -332,6 +340,37 @@ func IndexedFeatures(step []string, wanted map[string]bool) []IndexedFeature {
return result
}
// FeatureSchedule returns the features present in the union of sourceBiomes
// for one decoration stage. allBiomeOrder is the complete BiomeSource encounter
// order used to build FeatureSorter, not the order of the local source region.
// The result is stable and contains each placed feature at most once.
func (s *FeatureSet) FeatureSchedule(allBiomeOrder, sourceBiomes []string, stage int) ([]ScheduledFeature, error) {
steps, err := s.FeatureSteps(allBiomeOrder)
if err != nil {
return nil, err
}
if stage < 0 || stage >= len(steps) {
return nil, fmt.Errorf("worldgen: feature stage %d out of range", stage)
}
wanted := make(map[string]bool)
for _, biomeName := range sourceBiomes {
biome, ok := s.Biomes[biomeName]
if !ok || stage >= len(biome.Features) {
continue
}
for _, name := range biome.Features[stage] {
wanted[name] = true
}
}
result := make([]ScheduledFeature, 0, len(wanted))
for index, name := range steps[stage] {
if wanted[name] {
result = append(result, ScheduledFeature{Name: name, Index: index})
}
}
return result, nil
}
func (s *FeatureSet) Ore(name string) (OreFeatureConfig, error) {
configured, ok := s.Configured[name]
if !ok || configured.Type != "minecraft:ore" {

View file

@ -82,6 +82,44 @@ func TestFeatureStepsRejectsCycles(t *testing.T) {
}
}
func TestFeatureScheduleUsesGlobalOrderAndSourceUnion(t *testing.T) {
set := &FeatureSet{Biomes: map[string]BiomeGeneration{
"a": {Features: [][]string{{"f1"}, {"f3", "f4"}}},
"b": {Features: [][]string{{"f2", "f1"}, {"f5", "f4"}}},
"c": {Features: [][]string{{"f1"}, {"f5", "f3"}}},
}}
got, err := set.FeatureSchedule([]string{"a", "b", "c"}, []string{"b", "c"}, 1)
if err != nil {
t.Fatal(err)
}
want := []ScheduledFeature{{Name: "f5", Index: 0}, {Name: "f3", Index: 1}, {Name: "f4", Index: 2}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("schedule = %v, want %v", got, want)
}
}
func TestFeatureScheduleDoesNotRenumberFilteredFeatures(t *testing.T) {
set := &FeatureSet{Biomes: map[string]BiomeGeneration{
"a": {Features: [][]string{{"f1", "f2", "f3"}}},
"b": {Features: [][]string{{"f3"}}},
}}
got, err := set.FeatureSchedule([]string{"a", "b"}, []string{"b"}, 0)
if err != nil {
t.Fatal(err)
}
want := []ScheduledFeature{{Name: "f3", Index: 2}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("schedule = %v, want global index %v", got, want)
}
}
func TestFeatureScheduleRejectsInvalidStage(t *testing.T) {
set := &FeatureSet{Biomes: map[string]BiomeGeneration{"a": {Features: [][]string{{"f1"}}}}}
if _, err := set.FeatureSchedule([]string{"a"}, []string{"a"}, 1); err == nil {
t.Fatal("invalid stage succeeded")
}
}
func TestPlacementHeightDistributionsStayWithinInclusiveBounds(t *testing.T) {
r := NewLegacy(12345)
for _, distribution := range []string{"minecraft:trapezoid", "minecraft:very_biased_to_bottom", "minecraft:uniform"} {