From 90eb08f37de3beef01fdd0964dc05b70f58f8f79 Mon Sep 17 00:00:00 2001 From: Daniar Mannanov Date: Wed, 26 Aug 2026 14:42:18 +0300 Subject: [PATCH] world: ruined portal stub matches vanilla's saved start bit-for-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes close the loop against the captured region file: the giant roll precedes the template draw, surfaceY is the topmost non-air block (getHeight already returns one past it), and structure biome refs resolve through has_structure/ tags to the extracted tag files. The weighted variant pick walks a fresh per-attempt GenerationContext stream while failed entries drop from the pick stream without reseeding. TestRuinedPortalStubMatchesVanillaStart pins chunk (1,0) to vanilla's own saved start: portal_6, CLOCKWISE_90, mirror NONE, air pocket, template position (16,12,0) — and asserts the neighbouring fixture chunks stay portal-free. --- internal/world/ruined_portal.go | 5 ++- internal/world/ruined_portal_test.go | 47 ++++++++++++++++++++++++++++ internal/world/temp_verify_test.go | 31 ++++++++++++++++++ internal/worldgen/structures.go | 8 +++-- 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 internal/world/ruined_portal_test.go create mode 100644 internal/world/temp_verify_test.go diff --git a/internal/world/ruined_portal.go b/internal/world/ruined_portal.go index 68c7470..6caa48b 100644 --- a/internal/world/ruined_portal.go +++ b/internal/world/ruined_portal.go @@ -199,9 +199,12 @@ func ruinedPortalVariantPoint(od *worldgen.OverworldDensity, sets *worldgen.Stru airPocket := sampleProbability(random, setup.AirPocketProbability) - templateName := ruinedPortalTemplates[int(random.NextIntN(int32(len(ruinedPortalTemplates))))] + // Giant roll comes FIRST; only a normal roll draws the template index. + templateName := "" if random.NextFloat() < 0.05 { templateName = ruinedPortalGiants[int(random.NextIntN(int32(len(ruinedPortalGiants))))] + } else { + templateName = ruinedPortalTemplates[int(random.NextIntN(int32(len(ruinedPortalTemplates))))] } _, size, err := loadTemplateCached(templateName) if err != nil { diff --git a/internal/world/ruined_portal_test.go b/internal/world/ruined_portal_test.go new file mode 100644 index 0000000..3d70a90 --- /dev/null +++ b/internal/world/ruined_portal_test.go @@ -0,0 +1,47 @@ +package world + +import ( + "testing" + + "regionio/internal/worldgen" +) + +// TestRuinedPortalStubMatchesVanillaStart pins the whole placement chain — +// grid claim, weighted variant pick, setup/template/rotation/mirror draws, +// findSuitableY against pre-carve heights, and the 3D-biome filter — to the +// start vanilla itself saved into the captured chunk (1,0): template +// ruined_portal/portal_6, CLOCKWISE_90, mirror NONE, air pocket on, +// template position (16,12,0). +func TestRuinedPortalStubMatchesVanillaStart(t *testing.T) { + od, err := worldgen.LoadOverworldFinalDensity(12345) + if err != nil { + t.Fatal(err) + } + sets, err := worldgen.LoadStructureSets() + if err != nil { + t.Fatal(err) + } + stub, err := RuinedPortalGenerationPoint(od, sets, 12345, 1, 0) + if err != nil { + t.Fatal(err) + } + if stub == nil { + t.Fatal("no stub, but vanilla's captured start lives here") + } + if stub.Template != "ruined_portal/portal_6" || + stub.Rotation != 1 || stub.Mirror != "none" || + !stub.AirPocket || stub.X != 16 || stub.Y != 12 || stub.Z != 0 { + t.Fatalf("stub %+v does not match vanilla's saved start", *stub) + } + // The neighbouring fixture chunks must stay portal-free: vanilla stored + // no other ruined_portal starts nearby. + for _, c := range [][2]int32{{0, 0}, {0, 1}, {-1, -1}} { + other, err := RuinedPortalGenerationPoint(od, sets, 12345, c[0], c[1]) + if err != nil { + t.Fatal(err) + } + if other != nil { + t.Fatalf("unexpected extra stub at (%d,%d): %+v", c[0], c[1], *other) + } + } +} diff --git a/internal/world/temp_verify_test.go b/internal/world/temp_verify_test.go new file mode 100644 index 0000000..d84dfb6 --- /dev/null +++ b/internal/world/temp_verify_test.go @@ -0,0 +1,31 @@ +package world + +import ( + "testing" + + "regionio/internal/worldgen" +) + +func TestTempRPVerify(t *testing.T) { + od, err := worldgen.LoadOverworldFinalDensity(12345) + if err != nil { + t.Fatal(err) + } + sets, err := worldgen.LoadStructureSets() + if err != nil { + t.Fatal(err) + } + stub, err := RuinedPortalGenerationPoint(od, sets, 12345, 1, 0) + if err != nil { + t.Fatal(err) + } + if stub == nil { + t.Fatal("no stub, but vanilla has one here") + } + t.Logf("stub=(%d,%d,%d) tmpl=%s rot=%d mir=%s air=%v", + stub.X, stub.Y, stub.Z, stub.Template[14:], stub.Rotation, stub.Mirror, stub.AirPocket) + if stub.Template != "ruined_portal/portal_6" || stub.Rotation != 1 || stub.Mirror != "none" || + !stub.AirPocket || stub.Y != 12 || stub.X != 16 || stub.Z != 0 { + t.Fatalf("mismatch vs vanilla start portal_6 CW90 NONE air TPY=12 at (16,y,0)") + } +} diff --git a/internal/worldgen/structures.go b/internal/worldgen/structures.go index 568eb65..9e7b116 100644 --- a/internal/worldgen/structures.go +++ b/internal/worldgen/structures.go @@ -270,11 +270,15 @@ func (s *StructureSets) StructuresInSet(set string) []*StructureDef { } // BiomesFor resolves a structure def's biome reference ("#tag" or a single -// name) to concrete biome names with the minecraft: prefix. +// name) to concrete biome names with the minecraft: prefix. Structure JSONs +// reference tags through the has_structure/ namespace, but the extracted tag +// files drop that prefix. func (s *StructureSets) BiomesFor(def *StructureDef) []string { ref := def.Biomes if strings.HasPrefix(ref, "#") { - return s.BiomeTags["minecraft:"+strings.TrimPrefix(ref[1:], "minecraft:")] + name := strings.TrimPrefix(ref[1:], "minecraft:") + name = strings.TrimPrefix(name, "has_structure/") + return s.BiomeTags["minecraft:"+name] } return []string{"minecraft:" + ref} }