world: port ruined portal generation point and expose pre-carve terrain
generateBaseTerrain now factors the noise+surface stage out of chunk generation so structure placement can read pre-carve heights exactly like vanilla's STRUCTURE_STARTS stage. RuinedPortalGenerationPoint replays the ruined_portals set: the weighted pick across all seven variants on its own stream, each attempt's setup/template/rotation/ mirror draws on a fresh GenerationContext stream (makeRandom reseeds per attempt, confirmed from bytecode), findSuitableY with corner-column settle scan against those heights, and the post-draw 3D-biome filter at quart-snapped coordinates. Known gap, documented in STRUCTURE_NOTES.md: no variant is accepted in the window around the fixture's portal yet while vanilla placed one; the biome/surface inputs need a vanilla-side probe to bisect. Not wired into the production replay until that closes.
This commit is contained in:
parent
6e04bd8bbc
commit
798a3ec7b4
6 changed files with 454 additions and 31 deletions
|
|
@ -55,19 +55,31 @@ Per set per chunk, in order:
|
|||
2. placement.isStructureChunk(state, x, z) must hold (the grid above).
|
||||
3. Single-entry sets go straight to tryGenerateStructure with **zero draws**.
|
||||
4. Multi-entry sets: `random = WorldgenRandom(Legacy(0));
|
||||
random.setLargeFeatureSeed(levelSeed, chunkX, chunkZ)` (the two-long XOR
|
||||
mix), then loop:
|
||||
random.setLargeFeatureSeed(levelSeed, chunkX, chunkPos.z)` (the two-long
|
||||
XOR mix) drives ONLY the weighted picks; then loop:
|
||||
- pick = random.nextInt(totalWeight over remaining entries)
|
||||
- walk entries subtracting weight; first negative wins;
|
||||
- tryGenerateStructure it; on success stop, else REMOVE the entry,
|
||||
total -= its weight, and repeat **without reseeding** (the stream keeps
|
||||
running).
|
||||
5. tryGenerateStructure -> Structure.generate builds its own
|
||||
`WorldgenRandom(LegacyRandomSource(0))` seeded with
|
||||
setLargeFeatureSeed(seed, chunkX, chunkZ) — that is the `context.random()`
|
||||
every findGenerationPoint draw reads from. Biome check happens inside
|
||||
generate via the structure's biomes HolderSet against the biome at the
|
||||
generation point (position-dependent, after the stub is found).
|
||||
total -= its weight, and repeat **without reseeding** the pick stream.
|
||||
5. tryGenerateStructure -> Structure.generate builds a GenerationContext whose
|
||||
random comes from `GenerationContext.makeRandom(seed, chunkPos)`:
|
||||
`new WorldgenRandom(new LegacyRandomSource(0))` +
|
||||
`setLargeFeatureSeed(seed, chunkX, chunkZ)`. Every attempt therefore starts
|
||||
from an identically seeded FRESH stream — attempts do NOT continue each
|
||||
other's streams.
|
||||
6. Structure.findValidGenerationPoint runs findGenerationPoint first and filters
|
||||
by isValidBiome AFTERWARDS: the 3D noise biome at the stub position
|
||||
(quart-snapped coordinates) against the structure's biome set.
|
||||
|
||||
Port status: the whole chain above plus findSuitableY live in
|
||||
world/ruined_portal.go, but nothing accepts in the ±3-chunk window around the
|
||||
fixture's portal yet — every variant lands on ocean or lush-caves biomes at
|
||||
its stub while vanilla accepted one here. Open leads, in order of suspicion:
|
||||
(a) our 3D biome sampling off the fixture's 4x4x4 lattice may diverge from
|
||||
vanilla's Climate sampler at arbitrary quart positions; (b) getBaseHeight
|
||||
semantics on water columns; (c) the settle-scan corner sampling. A Java probe
|
||||
dumping vanilla's own stub for seed 12345 chunk (1,0) would settle it
|
||||
decisively.
|
||||
|
||||
### findGenerationPoint, in draw order
|
||||
|
||||
|
|
|
|||
|
|
@ -97,3 +97,7 @@ func coldEnoughToSnow(biome string) bool {
|
|||
temperature, ok := biomeTemperature[biome]
|
||||
return ok && temperature < 0.15
|
||||
}
|
||||
|
||||
// ColdEnoughToSnow exposes the per-biome snow threshold to structure ports.
|
||||
func ColdEnoughToSnow(biome string) bool { return coldEnoughToSnow(biome) }
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,28 @@ type TemplateBlockInfo struct {
|
|||
HasNBT bool
|
||||
}
|
||||
|
||||
// ResolvedTemplate is a parsed .nbt structure with every palette entry mapped
|
||||
// to concrete state IDs through resolve.
|
||||
// LoadResolvedTemplate reads a template file from disk.
|
||||
func LoadResolvedTemplate(path string, resolve func(name string, props map[string]string) (uint16, bool)) ([]TemplateBlockInfo, [3]int, error) {
|
||||
raw, err := readMaybeGzip(path)
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, [3]int{}, err
|
||||
}
|
||||
raw, err = readMaybeGzipBytes(raw)
|
||||
if err != nil {
|
||||
return nil, [3]int{}, err
|
||||
}
|
||||
return LoadResolvedTemplateBytes(raw, resolve)
|
||||
}
|
||||
|
||||
// EmbeddedStructureTemplate reads a template from the embedded datapack data
|
||||
// by its resource-path name, e.g. "ruined_portal/portal_1".
|
||||
func EmbeddedStructureTemplate(name string) ([]byte, error) {
|
||||
return dataFS.ReadFile("data/structure_template/" + name + ".nbt")
|
||||
}
|
||||
|
||||
// LoadResolvedTemplateBytes parses template NBT already in memory.
|
||||
func LoadResolvedTemplateBytes(raw []byte, resolve func(name string, props map[string]string) (uint16, bool)) ([]TemplateBlockInfo, [3]int, error) {
|
||||
raw, err := readMaybeGzipBytes(raw)
|
||||
if err != nil {
|
||||
return nil, [3]int{}, err
|
||||
}
|
||||
|
|
@ -121,11 +139,7 @@ func LoadResolvedTemplate(path string, resolve func(name string, props map[strin
|
|||
return blocks, size, nil
|
||||
}
|
||||
|
||||
func readMaybeGzip(path string) ([]byte, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func readMaybeGzipBytes(b []byte) ([]byte, error) {
|
||||
if len(b) >= 2 && b[0] == 0x1f && b[1] == 0x8b {
|
||||
gr, err := gzip.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
|
|
@ -174,3 +188,4 @@ func MthGetSeed(x, y, z int) int64 {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -86,12 +86,42 @@ func (p *rawPlacement) decode() (*Placement, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// RuinedPortalSetup is one weighted entry of a ruined portal structure's
|
||||
// "setups" list.
|
||||
type RuinedPortalSetup struct {
|
||||
Placement string `json:"placement"`
|
||||
AirPocketProbability float32 `json:"air_pocket_probability"`
|
||||
Mossiness float32 `json:"mossiness"`
|
||||
Overgrown bool `json:"overgrown"`
|
||||
Vines bool `json:"vines"`
|
||||
ReplaceWithBlackstone bool `json:"replace_with_blackstone"`
|
||||
CanBeCold bool `json:"can_be_cold"`
|
||||
Weight float32 `json:"weight"`
|
||||
}
|
||||
|
||||
// StructureDef is one entry of data/minecraft/worldgen/structure.
|
||||
type StructureDef struct {
|
||||
Name string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Biomes string `json:"biomes"`
|
||||
Step string `json:"step"`
|
||||
Name string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Biomes string `json:"biomes"`
|
||||
Step string `json:"step"`
|
||||
Setups []RuinedPortalSetup `json:"-"`
|
||||
}
|
||||
|
||||
func decodeStructureDef(name string, raw []byte) (*StructureDef, error) {
|
||||
var doc struct {
|
||||
Type string `json:"type"`
|
||||
Biomes string `json:"biomes"`
|
||||
Step string `json:"step"`
|
||||
Setups []RuinedPortalSetup `json:"setups"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StructureDef{
|
||||
Name: name, Type: doc.Type, Biomes: strings.TrimPrefix(doc.Biomes, "minecraft:"),
|
||||
Step: doc.Step, Setups: doc.Setups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StructureSet is one entry of data/minecraft/worldgen/structure_set.
|
||||
|
|
@ -183,14 +213,12 @@ func loadStructureSets() (*StructureSets, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var def StructureDef
|
||||
if err := json.Unmarshal(raw, &def); err != nil {
|
||||
name := "minecraft:" + strings.TrimSuffix(baseName(path), ".json")
|
||||
def, err := decodeStructureDef(name, raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
name := "minecraft:" + strings.TrimSuffix(baseName(path), ".json")
|
||||
def.Name = name
|
||||
def.Biomes = strings.TrimPrefix(def.Biomes, "minecraft:")
|
||||
out.Structures[name] = &def
|
||||
out.Structures[name] = def
|
||||
}
|
||||
|
||||
setFiles, err := fs.Glob(dataFS, "data/structure_set/*.json")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue