Initial commit: Minecraft Fabric 模组:神经网络地形生成

This commit is contained in:
WpyQwq
2026-09-19 12:00:57 +08:00
commit 840b77b53a
70 changed files with 5024 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
+30
View File
@@ -0,0 +1,30 @@
# Automatically build the project and run any configured tests for every push
# and submitted pull request. This can help catch issues that only occur on
# certain platforms or Java versions, and provides a first line of defence
# against bad commits.
name: build
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: validate gradle wrapper
uses: gradle/actions/wrapper-validation@v6
- name: setup jdk
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'microsoft'
- name: make gradle wrapper executable
run: chmod +x ./gradlew
- name: build
run: ./gradlew build
- name: capture build artifacts
uses: actions/upload-artifact@v7
with:
name: Artifacts
path: build/libs/
+45
View File
@@ -0,0 +1,45 @@
# gradle
.gradle/
build/
out/
classes/
# eclipse
*.launch
# idea
.idea/
*.iml
*.ipr
*.iws
# vscode
.settings/
.vscode/
bin/
.classpath
.project
# macos
*.DS_Store
# fabric
run/
logs/
config/
data/
work/
tools/__pycache__/
# java
hs_err_*.log
replay_*.log
*.hprof
*.jfr
+121
View File
@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
+77
View File
@@ -0,0 +1,77 @@
# Neural Terrain
Fabric 1.20.1 mod that replaces the vanilla noise heightfield with a deterministic feed-forward neural terrain model.
## Current implementation
- Fabric Loader and Fabric API for Minecraft 1.20.1.
- Mixin into `NoiseBasedChunkGenerator.fillFromNoise` and short-circuit the vanilla noise terrain stage.
- Captures each server world's seed and feeds it into the model.
- External JSON MLP weights with an embedded thread-safe fallback; both produce surface height, moisture, and temperature.
- Converts predictions into bedrock, stone, dirt, grass/sand, water, and air columns.
- Maps the network's temperature/moisture head to vanilla ocean, desert, savanna, snowy, taiga, forest, jungle, swamp, and plains biomes.
- Runs a separate deterministic 3D neural field for cave probability using `(seed, x, y, z)` features.
- Can disable vanilla noise carvers so neural caves are not mixed with a second cave generator.
- Adds a deterministic model-driven decoration pass for extra ores, vegetation, and small stone ruins after vanilla biome decoration.
- Keeps the world seed deterministic and only changes newly generated chunks.
The model runtime is deliberately dependency-light: it uses JSON weights instead of native ONNX libraries, while preserving a stable `TerrainPrediction` contract.
## Build
Use Java 21 to run Gradle/Loom. The produced bytecode targets Java 17, matching Minecraft 1.20.1.
```powershell
$env:JAVA_HOME = 'C:\Program Files\Java\jdk-21.0.10'
.\gradlew.bat build
```
The remapped jar is written to `build/libs/`.
Run the headless generation smoke test without accepting the server EULA:
```powershell
.\gradlew.bat chunkSmoke
.\gradlew.bat biomeSmoke decorationSmoke jsonModelSmoke
```
On first launch the mod creates `config/neuralterrain.json`. Set `enabled` to `false` to restore vanilla noise generation without removing the mod. The other fields control sea level, model height scale, beach width, bedrock thickness, water generation, neural biome selection, neural caves, carver disabling, neural decorations, extra ores/vegetation/structures, cave threshold, and the external model filename. Restart the game after changing the file.
Operators with permission level 2 can use `/neuralterrain status` and `/neuralterrain reload` in-game. Reload affects newly generated chunks; already generated chunks are not rewritten.
Use `/neuralterrain export_samples 256 8` near a generated area to export real surface heights and biome climate features to `config/neuralterrain-samples.jsonl`. That file can be passed directly to the trainer.
Use `/neuralterrain export_cave_samples 128 4 -64 128` to export interior air/solid labels to `config/neuralterrain-cave-samples.jsonl`. The command samples only below the predicted surface and is intended for collecting cave training data from reference worlds.
## Test in the development client
```powershell
$env:JAVA_HOME = 'C:\Program Files\Java\jdk-21.0.10'
.\gradlew.bat runClient
```
Create a new world after launching the client. Existing chunks are not rewritten.
If the development client crashes in native GLFW before the menu appears, use the headless generation smoke tasks below; this is an environment-specific renderer issue and does not affect the server-side generation code.
For this specific development environment, `.\gradlew.bat runClient -PdevNoGlfwWait=true` enables a disabled-by-default client workaround that gets past the first GLFW wait calls long enough to verify world creation. It is not enabled in normal gameplay or in the packaged configuration.
## Replacing the model
If `config/neuralterrain-model.json` exists, it is loaded automatically. The terrain-height JSON schema is documented in `tools/train_neural_model.py`; if it is missing or invalid, the mod uses the embedded MLP fallback. Cave weights are optional: models without the optional `cave_*` arrays use the embedded 3D cave network, while models that include them can predict caves externally too.
The standard-library trainer accepts JSONL samples and writes a compatible model:
```powershell
python tools/train_neural_model.py `
--input tools/sample_training_samples.jsonl `
--cave-input tools/sample_cave_samples.jsonl `
--output run/config/neuralterrain-model.json `
--epochs 200 `
--cave-epochs 200
```
The sample files are only pipeline smoke tests. For useful terrain, replace them with measurements extracted from reference worlds or a curated terrain dataset. Omitting `--cave-input` keeps the embedded cave network as the fallback.
## Important limitation
This implementation covers neural terrain height, climate-driven surface blocks, model-selected vanilla biomes, water, neural caves, model-driven extra ores/vegetation, and small deterministic ruins. The standard vanilla structure/ore/vegetation stages remain enabled after biome selection. Actual in-game visual tuning still depends on training against a representative reference-world dataset.
+122
View File
@@ -0,0 +1,122 @@
plugins {
id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"
id 'maven-publish'
}
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
loom {
splitEnvironmentSourceSets()
mods {
"neuralterrain" {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings loom.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
}
processResources {
def version = project.version
inputs.property "version", version
filesMatching("fabric.mod.json") {
expand "version": version
}
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 17
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
jar {
def projectName = project.name
inputs.property "projectName", projectName
from("LICENSE") {
rename { "${it}_$projectName"}
}
}
// configure the maven publication
publishing {
publications {
create("mavenJava", MavenPublication) {
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}
tasks.register('chunkSmoke', JavaExec) {
dependsOn testClasses
classpath = sourceSets.test.runtimeClasspath
mainClass = 'com.neuralterrain.ChunkGenerationSmoke'
}
tasks.register('worldgenIntrospect', JavaExec) {
dependsOn testClasses
classpath = sourceSets.test.runtimeClasspath
mainClass = 'com.neuralterrain.WorldgenIntrospection'
}
tasks.register('biomeSmoke', JavaExec) {
dependsOn testClasses
classpath = sourceSets.test.runtimeClasspath
mainClass = 'com.neuralterrain.BiomeSelectionSmoke'
}
tasks.register('decorationSmoke', JavaExec) {
dependsOn testClasses
classpath = sourceSets.test.runtimeClasspath
mainClass = 'com.neuralterrain.DecorationSmoke'
}
tasks.register('jsonModelSmoke', JavaExec) {
dependsOn testClasses
classpath = sourceSets.test.runtimeClasspath
mainClass = 'com.neuralterrain.JsonModelSmoke'
}
tasks.named('runClient') {
if (project.findProperty('devNoGlfwWait') == 'true') {
jvmArgs '-Dneuralterrain.dev_no_glfw_wait=true'
}
}
tasks.named('test') {
failOnNoDiscoveredTests = false
}
+9
View File
@@ -0,0 +1,9 @@
#Indigo properties file
#Mon Aug 24 01:45:59 CST 2026
always-tesselate-blocks=auto
ambient-occlusion-mode=hybrid
debug-compare-lighting=auto
fix-exterior-vertex-lighting=auto
fix-luminous-block-ambient-occlusion=auto
fix-mean-light-calculation=auto
fix-smooth-lighting-offset=auto
+18
View File
@@ -0,0 +1,18 @@
{
"enabled": true,
"sea_level": 63,
"base_height": 68.0,
"height_amplitude": 46.0,
"beach_margin": 2,
"bedrock_layers": 2,
"generate_water": true,
"generate_neural_biomes": true,
"generate_neural_caves": true,
"disable_vanilla_carvers": true,
"generate_neural_decorations": true,
"generate_neural_ores": true,
"generate_neural_vegetation": true,
"generate_neural_structures": true,
"cave_threshold": 0.76,
"model_file": "neuralterrain-model.json"
}
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.parallel=true
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
org.gradle.configuration-cache=false
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.20.1
loader_version=0.16.10
loom_version=1.17-SNAPSHOT
# Mod Properties
version=1.0.0
group=com.neuralterrain
# Dependencies
fabric_api_version=0.92.11+1.20.1
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+82
View File
@@ -0,0 +1,82 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
@@ -0,0 +1,9 @@
#Indigo properties file
#Mon Aug 24 01:59:25 CST 2026
always-tesselate-blocks=auto
ambient-occlusion-mode=hybrid
debug-compare-lighting=auto
fix-exterior-vertex-lighting=auto
fix-luminous-block-ambient-occlusion=auto
fix-mean-light-calculation=auto
fix-smooth-lighting-offset=auto
+212
View File
@@ -0,0 +1,212 @@
{
"format": 1,
"inputs": 10,
"hidden": 12,
"hidden_weights": [
[
0.2754988831556094,
0.005471173520413314,
-0.31023426789934727,
0.27912783125741875,
0.24707666557180574,
0.08744277585964073,
0.08450698145757433,
-0.20550850033231097,
0.1937029717764104,
-0.16055724621831813
],
[
-0.2019300958225679,
0.1190747346433251,
-0.1697225410846959,
0.07915881551281527,
0.06268489945913523,
-0.2390587338042794,
-0.27243034149935047,
-0.38523141180668236,
-0.16235769706302114,
0.07623138019012304
],
[
0.29773495420111,
-0.23583725494094143,
0.03741515233486273,
-0.020222847507565436,
0.06543013001159817,
-0.22083352553855728,
0.01130782926797376,
-0.17326227922727455,
0.06472781021137101,
0.1090563047318285
],
[
0.028463204006248678,
-0.09793892147644355,
-0.22276881424000902,
0.21423449321626176,
-0.08597474723847656,
-0.002520779928116698,
-0.41001061084567175,
-0.1972776622976282,
-0.08653636098884486,
-0.054690325847315195
],
[
-0.018671339424764552,
-0.09624109285519246,
-0.2581245188762083,
0.07297216348700415,
-0.18278249738848057,
0.03953767114346451,
-0.20360877937694932,
0.4294551718579739,
0.14648909958434828,
0.24265805785470437
],
[
-0.2679938401051588,
-0.07633410849947883,
-0.14597903940691784,
-0.13942843315554693,
0.2511520168634119,
-0.16164406092955652,
-0.10895512461823413,
0.4231613590518645,
-0.2750718152992229,
0.13780473947700614
],
[
-0.2529484051524478,
-0.04101310816685554,
-0.14713347664757379,
-0.057226110462928394,
0.16207000742165623,
0.007295208246831616,
0.13643114594707967,
-0.10588222203837315,
0.2238823719114911,
0.2416886803073975
],
[
0.06983262606364496,
0.1817701267819151,
0.11245633537299114,
-0.059162661963387216,
-0.22812022308317706,
-0.1470694676692041,
-0.1479445498659549,
-0.39160391832040703,
0.13747589991844725,
0.24992826195897747
],
[
0.05102494543062491,
0.1838084887804154,
0.043726735680290625,
0.07435668362783411,
-0.11507856282318618,
0.12627812710863243,
0.07964748763313333,
-0.3404137626907646,
0.30124449823719274,
-0.08243528348033374
],
[
-0.25588035661805925,
0.18716503550380623,
0.16862758960980195,
0.12134591959965843,
-0.04159303156776821,
-0.2825069169124978,
-0.056220735570265815,
-0.15295831360139087,
-0.144569203874221,
-0.26018073181914875
],
[
-0.00017375050926075398,
-0.26431706844187736,
0.19738719719204162,
0.13734540343445262,
-0.14666415754100637,
0.24848209637889487,
-0.2617428595619079,
-0.16418042629747026,
-0.26354317962986934,
-0.043447851331193076
],
[
-0.07848122489220594,
-0.07310137288588316,
0.05079716688557638,
0.2801050954992891,
0.0065746437592551325,
0.20425362445588824,
-0.1592812507288359,
-0.004043685745564589,
0.10287540272125685,
0.16087120426868967
]
],
"hidden_bias": [
0.02451422940628436,
-0.034599682318164106,
0.006818132216127853,
-0.08383011035186456,
0.02568347083183008,
0.06265164173532399,
0.009304967146413226,
0.024410737208217816,
-0.03661836465898142,
-0.03728747437464612,
-0.015493304814290934,
0.009243785773283004
],
"height_weights": [
0.18052436358469373,
0.22200858053706948,
-0.0052842529438649945,
0.31993671239916505,
-0.16544469395014041,
-0.35359076923789723,
0.19955100516925087,
0.2376403131732161,
0.14611685974423327,
-0.14849332065629994,
0.1558779436408704,
0.2129015490040494
],
"moisture_weights": [
0.14586712111760133,
-0.2953894445406313,
-0.2431267673101488,
-0.2810465612024007,
0.12710608617351357,
0.16221418594154818,
0.05816936378083109,
-0.3654778591870911,
-0.22642862200907848,
0.02670901872352202,
0.16287140197058575,
-0.15641896222675553
],
"temperature_weights": [
0.247303528520998,
-0.05905329328842297,
0.1876885723149688,
0.26398983061780407,
-0.054587485683861,
0.13068871060946566,
-0.03734410262542635,
0.1735377976504967,
0.307159069995456,
0.22295026070643356,
-0.21234552107746762,
-0.11262532371477586
],
"height_bias": 0.1109664068968335,
"moisture_bias": 0.12753219503424906,
"temperature_bias": 0.0831592107145708,
"base_height": 68.0,
"height_amplitude": 46.0
}
+18
View File
@@ -0,0 +1,18 @@
{
"enabled": true,
"sea_level": 63,
"base_height": 68.0,
"height_amplitude": 46.0,
"beach_margin": 2,
"bedrock_layers": 2,
"generate_water": true,
"generate_neural_biomes": true,
"generate_neural_caves": true,
"disable_vanilla_carvers": true,
"generate_neural_decorations": true,
"generate_neural_ores": true,
"generate_neural_vegetation": true,
"generate_neural_structures": true,
"cave_threshold": 0.76,
"model_file": "neuralterrain-model.json"
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA).
#Mon Aug 24 00:16:14 CST 2026
eula=false
+137
View File
@@ -0,0 +1,137 @@
version:3465
autoJump:false
operatorItemsTab:false
autoSuggestions:true
chatColors:true
chatLinks:true
chatLinksPrompt:true
enableVsync:true
entityShadows:true
forceUnicodeFont:false
discrete_mouse_scroll:false
invertYMouse:false
realmsNotifications:true
reducedDebugInfo:false
showSubtitles:false
directionalAudio:false
touchscreen:false
fullscreen:false
bobView:true
toggleCrouch:false
toggleSprint:false
darkMojangStudiosBackground:false
hideLightningFlashes:false
mouseSensitivity:0.5
fov:0.0
screenEffectScale:1.0
fovEffectScale:1.0
darknessEffectScale:1.0
glintSpeed:0.5
glintStrength:0.75
damageTiltStrength:1.0
highContrast:false
gamma:0.5
renderDistance:12
simulationDistance:12
entityDistanceScaling:1.0
guiScale:0
particles:0
maxFps:120
graphicsMode:1
ao:true
prioritizeChunkUpdates:0
biomeBlendRadius:2
renderClouds:"true"
resourcePacks:["fabric"]
incompatibleResourcePacks:[]
lastServer:
lang:en_us
soundDevice:""
chatVisibility:0
chatOpacity:1.0
chatLineSpacing:0.0
textBackgroundOpacity:0.5
backgroundForChatOnly:true
hideServerAddress:false
advancedItemTooltips:false
pauseOnLostFocus:true
overrideWidth:0
overrideHeight:0
chatHeightFocused:1.0
chatDelay:0.0
chatHeightUnfocused:0.4375
chatScale:1.0
chatWidth:1.0
notificationDisplayTime:1.0
mipmapLevels:4
useNativeTransport:true
mainHand:"right"
attackIndicator:1
narrator:0
tutorialStep:movement
mouseWheelSensitivity:1.0
rawMouseInput:true
glDebugVerbosity:1
skipMultiplayerWarning:false
skipRealms32bitWarning:false
hideMatchedNames:true
joinedFirstServer:false
hideBundleTutorial:false
syncChunkWrites:true
showAutosaveIndicator:true
allowServerListing:true
onlyShowSecureChat:false
panoramaScrollSpeed:1.0
telemetryOptInExtra:false
onboardAccessibility:false
key_key.attack:key.mouse.left
key_key.use:key.mouse.right
key_key.forward:key.keyboard.w
key_key.left:key.keyboard.a
key_key.back:key.keyboard.s
key_key.right:key.keyboard.d
key_key.jump:key.keyboard.space
key_key.sneak:key.keyboard.left.shift
key_key.sprint:key.keyboard.left.control
key_key.drop:key.keyboard.q
key_key.inventory:key.keyboard.e
key_key.chat:key.keyboard.t
key_key.playerlist:key.keyboard.tab
key_key.pickItem:key.mouse.middle
key_key.command:key.keyboard.slash
key_key.socialInteractions:key.keyboard.p
key_key.screenshot:key.keyboard.f2
key_key.togglePerspective:key.keyboard.f5
key_key.smoothCamera:key.keyboard.unknown
key_key.fullscreen:key.keyboard.f11
key_key.spectatorOutlines:key.keyboard.unknown
key_key.swapOffhand:key.keyboard.f
key_key.saveToolbarActivator:key.keyboard.c
key_key.loadToolbarActivator:key.keyboard.x
key_key.advancements:key.keyboard.l
key_key.hotbar.1:key.keyboard.1
key_key.hotbar.2:key.keyboard.2
key_key.hotbar.3:key.keyboard.3
key_key.hotbar.4:key.keyboard.4
key_key.hotbar.5:key.keyboard.5
key_key.hotbar.6:key.keyboard.6
key_key.hotbar.7:key.keyboard.7
key_key.hotbar.8:key.keyboard.8
key_key.hotbar.9:key.keyboard.9
soundCategory_master:1.0
soundCategory_music:1.0
soundCategory_record:1.0
soundCategory_weather:1.0
soundCategory_block:1.0
soundCategory_hostile:1.0
soundCategory_neutral:1.0
soundCategory_player:1.0
soundCategory_ambient:1.0
soundCategory_voice:1.0
modelPart_cape:true
modelPart_jacket:true
modelPart_left_sleeve:true
modelPart_right_sleeve:true
modelPart_left_pants_leg:true
modelPart_right_pants_leg:true
modelPart_hat:true
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
☃
+58
View File
@@ -0,0 +1,58 @@
#Minecraft server properties
#Mon Aug 24 01:35:15 CST 2026
allow-flight=false
allow-nether=true
broadcast-console-to-ops=true
broadcast-rcon-to-ops=true
difficulty=easy
enable-command-block=false
enable-jmx-monitoring=false
enable-query=false
enable-rcon=false
enable-status=true
enforce-secure-profile=true
enforce-whitelist=false
entity-broadcast-range-percentage=100
force-gamemode=false
function-permission-level=2
gamemode=survival
generate-structures=true
generator-settings={}
hardcore=false
hide-online-players=false
initial-disabled-packs=
initial-enabled-packs=vanilla,fabric
level-name=world
level-seed=
level-type=minecraft\:normal
max-chained-neighbor-updates=1000000
max-players=20
max-tick-time=60000
max-world-size=29999984
motd=A Minecraft Server
network-compression-threshold=256
online-mode=true
op-permission-level=4
player-idle-timeout=0
prevent-proxy-connections=false
pvp=true
query.port=25565
rate-limit=0
rcon.password=
rcon.port=25575
require-resource-pack=false
resource-pack=
resource-pack-prompt=
resource-pack-sha1=
server-ip=
server-port=25565
simulation-distance=10
spawn-animals=true
spawn-monsters=true
spawn-npcs=true
spawn-protection=16
sync-chunk-writes=true
text-filtering-config=
use-native-transport=true
view-distance=10
white-list=false
+1
View File
@@ -0,0 +1 @@
[{"name":"Player943","uuid":"2250da04-2be8-3508-9a2e-a09e550d0033","expiresOn":"2026-09-24 02:01:14 +0800"}]
+13
View File
@@ -0,0 +1,13 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
// Should match your modid
rootProject.name = 'neural-terrain'
@@ -0,0 +1,18 @@
package com.neuralterrain.mixin;
import com.mojang.blaze3d.systems.RenderSystem;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Development-only workaround for environments whose GLFW frame wait crashes. */
@Mixin(RenderSystem.class)
public abstract class RenderSystemDevMixin {
@Inject(method = "limitDisplayFPS", at = @At("HEAD"), cancellable = true)
private static void neuralterrain$disableFrameWait(int limit, CallbackInfo ci) {
if (Boolean.getBoolean("neuralterrain.dev_no_glfw_wait")) {
ci.cancel();
}
}
}
@@ -0,0 +1,18 @@
package com.neuralterrain.mixin;
import com.mojang.blaze3d.platform.Window;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/** Development-only workaround for a native glfwWindowShouldClose crash. */
@Mixin(Window.class)
public abstract class WindowDevMixin {
@Inject(method = "shouldClose", at = @At("HEAD"), cancellable = true)
private void neuralterrain$avoidNativeShouldClose(CallbackInfoReturnable<Boolean> cir) {
if (Boolean.getBoolean("neuralterrain.dev_no_glfw_wait")) {
cir.setReturnValue(false);
}
}
}
@@ -0,0 +1,105 @@
package com.neuralterrain;
import com.neuralterrain.config.NeuralTerrainConfig;
import com.neuralterrain.model.NeuralTerrainModel;
import com.neuralterrain.tools.TerrainSampleExporter;
import com.neuralterrain.tools.CaveSampleExporter;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Entry point for the Neural Terrain mod. */
public final class NeuralTerrainMod implements ModInitializer {
public static final String MOD_ID = "neuralterrain";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitialize() {
NeuralTerrainConfig.load();
NeuralTerrainModel.load(NeuralTerrainConfig.modelPath());
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> registerCommands(dispatcher));
LOGGER.info("Neural Terrain loaded: deterministic neural heightfield generation is enabled.");
}
private static void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal("neuralterrain")
.requires(source -> source.hasPermission(2))
.then(Commands.literal("status")
.executes(context -> {
context.getSource().sendSuccess(
() -> Component.literal("Neural Terrain: " + NeuralTerrainConfig.describe() + ", backend=" + NeuralTerrainModel.backendName()),
false
);
return 1;
}))
.then(Commands.literal("reload")
.executes(context -> {
NeuralTerrainConfig.load();
NeuralTerrainModel.load(NeuralTerrainConfig.modelPath());
context.getSource().sendSuccess(
() -> Component.literal("Neural Terrain configuration reloaded."),
true
);
return 1;
}))
.then(Commands.literal("export_samples")
.then(Commands.argument("radius", IntegerArgumentType.integer(16, 512))
.then(Commands.argument("stride", IntegerArgumentType.integer(1, 32))
.executes(context -> {
BlockPos center = BlockPos.containing(context.getSource().getPosition());
int radius = IntegerArgumentType.getInteger(context, "radius");
int stride = IntegerArgumentType.getInteger(context, "stride");
try {
TerrainSampleExporter.ExportResult result = TerrainSampleExporter.export(
context.getSource().getLevel(), center, radius, stride
);
context.getSource().sendSuccess(
() -> Component.literal("Exported " + result.samples() + " terrain samples to " + result.path()),
true
);
return result.samples();
} catch (Exception exception) {
LOGGER.error("Terrain sample export failed", exception);
context.getSource().sendFailure(Component.literal("Terrain sample export failed: " + exception.getMessage()));
return 0;
}
}))))
.then(Commands.literal("export_cave_samples")
.then(Commands.argument("radius", IntegerArgumentType.integer(16, 256))
.then(Commands.argument("stride", IntegerArgumentType.integer(1, 8))
.then(Commands.argument("min_y", IntegerArgumentType.integer(-64, 320))
.then(Commands.argument("max_y", IntegerArgumentType.integer(-64, 320))
.executes(context -> {
BlockPos center = BlockPos.containing(context.getSource().getPosition());
int radius = IntegerArgumentType.getInteger(context, "radius");
int stride = IntegerArgumentType.getInteger(context, "stride");
int minY = IntegerArgumentType.getInteger(context, "min_y");
int maxY = IntegerArgumentType.getInteger(context, "max_y");
if (minY >= maxY) {
context.getSource().sendFailure(Component.literal("min_y must be below max_y"));
return 0;
}
try {
CaveSampleExporter.ExportResult result = CaveSampleExporter.export(
context.getSource().getLevel(), center, radius, stride, minY, maxY
);
context.getSource().sendSuccess(
() -> Component.literal("Exported " + result.samples() + " cave samples to " + result.path()),
true
);
return result.samples();
} catch (Exception exception) {
LOGGER.error("Cave sample export failed", exception);
context.getSource().sendFailure(Component.literal("Cave sample export failed: " + exception.getMessage()));
return 0;
}
})))))));
}
}
@@ -0,0 +1,221 @@
package com.neuralterrain.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.neuralterrain.NeuralTerrainMod;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import net.fabricmc.loader.api.FabricLoader;
/** Small dependency-free JSON configuration for the generation backend. */
public final class NeuralTerrainConfig {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final String FILE_NAME = "neuralterrain.json";
private static volatile boolean enabled = true;
private static volatile int seaLevel = 63;
private static volatile double baseHeight = 68.0;
private static volatile double heightAmplitude = 46.0;
private static volatile int beachMargin = 2;
private static volatile int bedrockLayers = 2;
private static volatile boolean generateWater = true;
private static volatile boolean generateNeuralBiomes = true;
private static volatile boolean generateNeuralCaves = true;
private static volatile boolean disableVanillaCarvers = true;
private static volatile boolean generateNeuralDecorations = true;
private static volatile boolean generateNeuralOres = true;
private static volatile boolean generateNeuralVegetation = true;
private static volatile boolean generateNeuralStructures = true;
private static volatile double caveThreshold = 0.76;
private static volatile String modelFile = "neuralterrain-model.json";
private NeuralTerrainConfig() {
}
public static void load() {
Path path = FabricLoader.getInstance().getConfigDir().resolve(FILE_NAME);
try {
Files.createDirectories(path.getParent());
if (Files.notExists(path)) {
Files.writeString(path, GSON.toJson(defaults()), StandardCharsets.UTF_8);
NeuralTerrainMod.LOGGER.info("Created default configuration at {}", path);
}
JsonObject json = JsonParser.parseString(Files.readString(path, StandardCharsets.UTF_8)).getAsJsonObject();
enabled = bool(json, "enabled", true);
seaLevel = integer(json, "sea_level", 63, -64, 320);
baseHeight = number(json, "base_height", 68.0, -32.0, 320.0);
heightAmplitude = number(json, "height_amplitude", 46.0, 1.0, 256.0);
beachMargin = integer(json, "beach_margin", 2, 0, 32);
bedrockLayers = integer(json, "bedrock_layers", 2, 1, 8);
generateWater = bool(json, "generate_water", true);
generateNeuralBiomes = bool(json, "generate_neural_biomes", true);
generateNeuralCaves = bool(json, "generate_neural_caves", true);
disableVanillaCarvers = bool(json, "disable_vanilla_carvers", true);
generateNeuralDecorations = bool(json, "generate_neural_decorations", true);
generateNeuralOres = bool(json, "generate_neural_ores", true);
generateNeuralVegetation = bool(json, "generate_neural_vegetation", true);
generateNeuralStructures = bool(json, "generate_neural_structures", true);
caveThreshold = number(json, "cave_threshold", 0.76, 0.5, 0.99);
modelFile = text(json, "model_file", "neuralterrain-model.json");
} catch (Exception exception) {
NeuralTerrainMod.LOGGER.error("Could not load {}, using safe defaults", path, exception);
resetToDefaults();
}
}
public static boolean enabled() {
return enabled;
}
public static int seaLevel() {
return seaLevel;
}
public static double baseHeight() {
return baseHeight;
}
public static double heightAmplitude() {
return heightAmplitude;
}
public static int beachMargin() {
return beachMargin;
}
public static int bedrockLayers() {
return bedrockLayers;
}
public static boolean generateWater() {
return generateWater;
}
public static boolean generateNeuralBiomes() {
return generateNeuralBiomes;
}
public static boolean generateNeuralCaves() {
return generateNeuralCaves;
}
public static boolean disableVanillaCarvers() {
return disableVanillaCarvers;
}
public static boolean generateNeuralDecorations() {
return generateNeuralDecorations;
}
public static boolean generateNeuralOres() {
return generateNeuralOres;
}
public static boolean generateNeuralVegetation() {
return generateNeuralVegetation;
}
public static boolean generateNeuralStructures() {
return generateNeuralStructures;
}
public static double caveThreshold() {
return caveThreshold;
}
public static Path modelPath() {
return FabricLoader.getInstance().getConfigDir().resolve(modelFile).normalize();
}
public static Path samplesPath() {
return FabricLoader.getInstance().getConfigDir().resolve("neuralterrain-samples.jsonl").normalize();
}
public static Path caveSamplesPath() {
return FabricLoader.getInstance().getConfigDir().resolve("neuralterrain-cave-samples.jsonl").normalize();
}
public static String describe() {
return "enabled=" + enabled
+ ", sea_level=" + seaLevel
+ ", base_height=" + baseHeight
+ ", height_amplitude=" + heightAmplitude
+ ", beach_margin=" + beachMargin
+ ", bedrock_layers=" + bedrockLayers
+ ", generate_water=" + generateWater
+ ", generate_neural_biomes=" + generateNeuralBiomes
+ ", generate_neural_caves=" + generateNeuralCaves
+ ", disable_vanilla_carvers=" + disableVanillaCarvers
+ ", generate_neural_decorations=" + generateNeuralDecorations
+ ", generate_neural_ores=" + generateNeuralOres
+ ", generate_neural_vegetation=" + generateNeuralVegetation
+ ", generate_neural_structures=" + generateNeuralStructures
+ ", cave_threshold=" + caveThreshold
+ ", model_file=" + modelFile;
}
private static JsonObject defaults() {
JsonObject json = new JsonObject();
json.addProperty("enabled", true);
json.addProperty("sea_level", 63);
json.addProperty("base_height", 68.0);
json.addProperty("height_amplitude", 46.0);
json.addProperty("beach_margin", 2);
json.addProperty("bedrock_layers", 2);
json.addProperty("generate_water", true);
json.addProperty("generate_neural_biomes", true);
json.addProperty("generate_neural_caves", true);
json.addProperty("disable_vanilla_carvers", true);
json.addProperty("generate_neural_decorations", true);
json.addProperty("generate_neural_ores", true);
json.addProperty("generate_neural_vegetation", true);
json.addProperty("generate_neural_structures", true);
json.addProperty("cave_threshold", 0.76);
json.addProperty("model_file", "neuralterrain-model.json");
return json;
}
private static void resetToDefaults() {
enabled = true;
seaLevel = 63;
baseHeight = 68.0;
heightAmplitude = 46.0;
beachMargin = 2;
bedrockLayers = 2;
generateWater = true;
generateNeuralBiomes = true;
generateNeuralCaves = true;
disableVanillaCarvers = true;
generateNeuralDecorations = true;
generateNeuralOres = true;
generateNeuralVegetation = true;
generateNeuralStructures = true;
caveThreshold = 0.76;
modelFile = "neuralterrain-model.json";
}
private static boolean bool(JsonObject json, String key, boolean fallback) {
return json.has(key) ? json.get(key).getAsBoolean() : fallback;
}
private static String text(JsonObject json, String key, String fallback) {
if (!json.has(key)) {
return fallback;
}
String value = json.get(key).getAsString().trim();
return value.isEmpty() || value.contains("..") || value.contains("\\") || value.contains("/") ? fallback : value;
}
private static int integer(JsonObject json, String key, int fallback, int min, int max) {
int value = json.has(key) ? json.get(key).getAsInt() : fallback;
return Math.max(min, Math.min(max, value));
}
private static double number(JsonObject json, String key, double fallback, double min, double max) {
double value = json.has(key) ? json.get(key).getAsDouble() : fallback;
return Math.max(min, Math.min(max, value));
}
}
@@ -0,0 +1,28 @@
package com.neuralterrain.mixin;
import com.neuralterrain.config.NeuralTerrainConfig;
import com.neuralterrain.world.NeuralDecoration;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Adds the model-driven decoration pass after vanilla biome features have run. */
@Mixin(ChunkGenerator.class)
public abstract class ChunkGeneratorMixin {
@Inject(method = "applyBiomeDecoration", at = @At("RETURN"))
private void neuralterrain$decorate(
WorldGenLevel level,
ChunkAccess chunk,
StructureManager structureManager,
CallbackInfo ci
) {
if (NeuralTerrainConfig.enabled() && NeuralTerrainConfig.generateNeuralDecorations()) {
NeuralDecoration.decorate(level, chunk, level.getSeed());
}
}
}
@@ -0,0 +1,100 @@
package com.neuralterrain.mixin;
import com.neuralterrain.config.NeuralTerrainConfig;
import com.neuralterrain.world.NeuralTerrainContext;
import com.neuralterrain.world.NeuralTerrainBiomes;
import com.neuralterrain.world.NeuralTerrainRewriter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import net.minecraft.world.level.StructureManager;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.server.level.WorldGenRegion;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Prevents vanilla noise terrain from running and supplies the neural heightfield directly. */
@Mixin(NoiseBasedChunkGenerator.class)
public abstract class NoiseBasedChunkGeneratorMixin {
@Inject(method = "createBiomes", at = @At("HEAD"), cancellable = true)
private void neuralterrain$rewriteBiomes(
Executor executor,
RandomState randomState,
Blender blender,
StructureManager structureManager,
ChunkAccess chunk,
CallbackInfoReturnable<CompletableFuture<ChunkAccess>> cir
) {
if (!NeuralTerrainConfig.enabled() || !NeuralTerrainConfig.generateNeuralBiomes()) {
return;
}
long seed = NeuralTerrainContext.seedFor((NoiseBasedChunkGenerator) (Object) this);
int minY = chunk.getMinBuildHeight();
int maxY = minY + chunk.getHeight();
Registry<Biome> biomeRegistry = structureManager.registryAccess().registryOrThrow(Registries.BIOME);
chunk.fillBiomesFromNoise((x, y, z, sampler) -> {
var prediction = com.neuralterrain.model.NeuralTerrainModel.predict(seed, x, z, minY, maxY);
return NeuralTerrainBiomes.select(biomeRegistry, prediction);
}, null);
cir.setReturnValue(CompletableFuture.completedFuture(chunk));
}
@Inject(method = "fillFromNoise", at = @At("HEAD"), cancellable = true)
private void neuralterrain$rewriteTerrain(
Executor executor,
Blender blender,
RandomState randomState,
StructureManager structureManager,
ChunkAccess chunk,
CallbackInfoReturnable<CompletableFuture<ChunkAccess>> cir
) {
if (!NeuralTerrainConfig.enabled()) {
return;
}
long seed = NeuralTerrainContext.seedFor((NoiseBasedChunkGenerator) (Object) this);
cir.setReturnValue(CompletableFuture.completedFuture(NeuralTerrainRewriter.rewrite(chunk, seed)));
}
@Inject(
method = "buildSurface(Lnet/minecraft/server/level/WorldGenRegion;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/levelgen/RandomState;Lnet/minecraft/world/level/chunk/ChunkAccess;)V",
at = @At("HEAD"),
cancellable = true
)
private void neuralterrain$skipVanillaSurface(
WorldGenRegion region,
StructureManager structureManager,
RandomState randomState,
ChunkAccess chunk,
org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci
) {
if (NeuralTerrainConfig.enabled()) {
ci.cancel();
}
}
@Inject(method = "applyCarvers", at = @At("HEAD"), cancellable = true)
private void neuralterrain$skipVanillaCarvers(
WorldGenRegion region,
long seed,
RandomState randomState,
BiomeManager biomeManager,
StructureManager structureManager,
ChunkAccess chunk,
GenerationStep.Carving carvingStep,
CallbackInfo ci
) {
if (NeuralTerrainConfig.enabled() && NeuralTerrainConfig.disableVanillaCarvers()) {
ci.cancel();
}
}
}
@@ -0,0 +1,18 @@
package com.neuralterrain.mixin;
import com.neuralterrain.world.NeuralTerrainContext;
import net.minecraft.server.level.ServerLevel;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Captures the world seed once the server chunk source has been created. */
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin {
@Inject(method = "<init>", at = @At("RETURN"))
private void neuralterrain$captureWorldSeed(CallbackInfo ci) {
ServerLevel level = (ServerLevel) (Object) this;
NeuralTerrainContext.register(level.getChunkSource().getGenerator(), level.getSeed());
}
}
@@ -0,0 +1,161 @@
package com.neuralterrain.model;
import com.neuralterrain.config.NeuralTerrainConfig;
/** Built-in fallback network used when no external weights are available. */
public final class EmbeddedTerrainModel implements TerrainModelBackend {
private static final int INPUTS = 10;
private static final int HIDDEN = 12;
private static final int CAVE_INPUTS = 8;
private static final int CAVE_HIDDEN = 10;
private static final float[][] HIDDEN_WEIGHTS = {
{ 0.82f, -0.11f, 0.34f, 0.18f, 0.42f, -0.21f, 0.08f, 0.14f, -0.05f, 0.12f },
{-0.31f, 0.74f, 0.17f, 0.29f, -0.23f, 0.18f, 0.11f, -0.08f, 0.13f, 0.04f },
{ 0.21f, 0.33f, 0.69f, -0.12f, 0.31f, 0.26f, -0.14f, 0.07f, 0.18f, -0.09f },
{-0.55f, 0.18f, 0.08f, 0.81f, 0.22f, -0.32f, 0.09f, 0.16f, -0.12f, 0.05f },
{ 0.15f, -0.46f, 0.38f, 0.21f, 0.72f, 0.12f, 0.03f, -0.11f, 0.09f, 0.16f },
{ 0.42f, 0.17f, -0.28f, 0.34f, 0.11f, 0.65f, -0.07f, 0.05f, 0.15f, -0.18f },
{-0.19f, 0.52f, 0.14f, -0.37f, 0.26f, 0.23f, 0.69f, 0.08f, -0.06f, 0.11f },
{ 0.27f, -0.13f, 0.44f, 0.31f, -0.16f, 0.18f, 0.09f, 0.73f, 0.12f, 0.07f },
{ 0.11f, 0.24f, -0.17f, 0.46f, 0.33f, -0.09f, 0.16f, 0.05f, 0.77f, -0.03f },
{-0.08f, 0.35f, 0.29f, -0.15f, 0.18f, 0.21f, -0.11f, 0.14f, 0.06f, 0.82f },
{ 0.36f, 0.09f, 0.22f, 0.15f, 0.41f, 0.07f, 0.18f, -0.04f, 0.17f, -0.12f },
{-0.24f, 0.28f, 0.31f, 0.19f, -0.34f, 0.11f, 0.24f, 0.13f, -0.08f, 0.29f }
};
private static final float[] HIDDEN_BIAS = {
0.08f, -0.04f, 0.11f, -0.07f, 0.03f, 0.06f,
-0.02f, 0.05f, -0.06f, 0.04f, 0.02f, -0.03f
};
private static final float[] HEIGHT_WEIGHTS = {
0.42f, -0.18f, 0.31f, 0.27f, 0.22f, 0.38f,
0.19f, -0.12f, 0.25f, 0.17f, 0.33f, -0.21f
};
private static final float[] MOISTURE_WEIGHTS = {
-0.12f, 0.28f, 0.17f, -0.25f, 0.09f, 0.21f,
0.18f, 0.31f, -0.14f, 0.23f, -0.08f, 0.16f
};
private static final float[] TEMPERATURE_WEIGHTS = {
0.21f, 0.07f, -0.19f, 0.22f, 0.14f, -0.11f,
0.26f, -0.04f, 0.18f, 0.13f, 0.09f, 0.24f
};
private static final float[][] CAVE_HIDDEN_WEIGHTS = {
{ 0.72f, -0.18f, 0.31f, 0.44f, 0.16f, -0.62f, 0.27f, -0.11f },
{-0.41f, 0.67f, 0.28f, -0.22f, 0.54f, 0.18f, -0.33f, 0.21f },
{ 0.36f, 0.22f, -0.73f, 0.19f, 0.12f, 0.51f, 0.42f, -0.28f },
{ 0.18f, -0.51f, 0.47f, 0.63f, -0.37f, 0.26f, 0.14f, 0.45f },
{-0.64f, 0.29f, 0.15f, -0.39f, 0.71f, -0.14f, 0.38f, 0.17f },
{ 0.23f, 0.48f, 0.52f, -0.18f, -0.55f, 0.33f, -0.21f, 0.69f },
{ 0.55f, -0.37f, 0.08f, 0.24f, 0.49f, 0.58f, -0.44f, -0.16f },
{-0.27f, 0.16f, 0.61f, 0.35f, 0.28f, -0.43f, 0.73f, 0.12f },
{ 0.43f, 0.34f, -0.26f, 0.57f, -0.18f, 0.41f, 0.19f, 0.62f },
{-0.12f, 0.59f, 0.39f, -0.47f, 0.33f, 0.22f, 0.52f, -0.36f }
};
private static final float[] CAVE_HIDDEN_BIAS = {
0.04f, -0.08f, 0.03f, 0.06f, -0.02f,
0.05f, -0.04f, 0.02f, 0.07f, -0.06f
};
private static final float[] CAVE_OUTPUT_WEIGHTS = {
0.83f, 0.66f, 0.72f, 0.58f, 0.69f,
0.61f, 0.77f, 0.64f, 0.55f, 0.74f
};
@Override
public TerrainPrediction predict(long seed, int worldX, int worldZ, int minY, int maxY) {
double[] input = features(seed, worldX, worldZ);
double[] hidden = new double[HIDDEN];
for (int h = 0; h < HIDDEN; h++) {
double sum = HIDDEN_BIAS[h];
for (int i = 0; i < INPUTS; i++) {
sum += HIDDEN_WEIGHTS[h][i] * input[i];
}
hidden[h] = Math.tanh(sum);
}
double height = output(hidden, HEIGHT_WEIGHTS, 0.0);
double moisture = sigmoid(output(hidden, MOISTURE_WEIGHTS, 0.0));
double temperature = sigmoid(output(hidden, TEMPERATURE_WEIGHTS, 0.0));
return toPrediction(height, moisture, temperature, minY, maxY, NeuralTerrainConfig.baseHeight(), NeuralTerrainConfig.heightAmplitude());
}
@Override
public float caveProbability(long seed, int worldX, int y, int worldZ) {
double[] input = caveFeatures(seed, worldX, y, worldZ);
double[] hidden = new double[CAVE_HIDDEN];
for (int h = 0; h < CAVE_HIDDEN; h++) {
double sum = CAVE_HIDDEN_BIAS[h];
for (int i = 0; i < CAVE_INPUTS; i++) {
sum += CAVE_HIDDEN_WEIGHTS[h][i] * input[i];
}
hidden[h] = Math.tanh(sum);
}
return (float) sigmoid(2.0 * output(hidden, CAVE_OUTPUT_WEIGHTS, -0.08) - 1.35);
}
@Override
public String name() {
return "embedded-mlp";
}
static double[] features(long seed, int worldX, int worldZ) {
double x = worldX;
double z = worldZ;
long foldedSeed = seed ^ (seed >>> 33) ^ (seed << 11);
double seedOffset = foldedSeed * 0.00000017;
return new double[] {
Math.sin(x * 0.0041),
Math.cos(x * 0.0041),
Math.sin(z * 0.0037),
Math.cos(z * 0.0037),
Math.sin((x + z) * 0.0023),
Math.cos((x - z) * 0.0027),
Math.sin(x * 0.013 + z * 0.009),
Math.cos(x * 0.011 - z * 0.015),
Math.sin((x + seedOffset) * 0.00091),
Math.cos((z - seedOffset) * 0.00107)
};
}
static double[] caveFeatures(long seed, int worldX, int y, int worldZ) {
long foldedSeed = seed ^ (seed >>> 29) ^ (seed << 17);
double seedOffset = foldedSeed * 0.00000011;
double x = worldX + seedOffset;
double z = worldZ - seedOffset;
return new double[] {
Math.sin(x * 0.035),
Math.cos(x * 0.035),
Math.sin(z * 0.037),
Math.cos(z * 0.037),
Math.sin(y * 0.12),
Math.cos(y * 0.12),
Math.sin((x + z + y) * 0.018),
Math.cos((x - z + y) * 0.021)
};
}
static TerrainPrediction toPrediction(double height, double moisture, double temperature, int minY, int maxY, double baseHeight, double amplitude) {
int usableHeight = Math.max(32, maxY - minY - 8);
int surface = (int) Math.round(baseHeight + height * amplitude);
surface = Math.max(minY + 6, Math.min(maxY - 4, Math.min(minY + usableHeight, surface)));
return new TerrainPrediction(surface, (float) moisture, (float) temperature);
}
static double output(double[] hidden, float[] weights, double bias) {
double sum = bias;
for (int i = 0; i < hidden.length; i++) {
sum += hidden[i] * weights[i];
}
return Math.tanh(sum);
}
static double sigmoid(double value) {
return 1.0 / (1.0 + Math.exp(-value * 2.0));
}
}
@@ -0,0 +1,153 @@
package com.neuralterrain.model;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
/** Loads a small dense MLP exported by tools/train_neural_model.py. */
public final class JsonTerrainModel implements TerrainModelBackend {
private static final EmbeddedTerrainModel EMBEDDED_CAVE_FALLBACK = new EmbeddedTerrainModel();
private final int inputCount;
private final int hiddenCount;
private final double[][] hiddenWeights;
private final double[] hiddenBias;
private final double[] heightWeights;
private final double[] moistureWeights;
private final double[] temperatureWeights;
private final double heightBias;
private final double moistureBias;
private final double temperatureBias;
private final double baseHeight;
private final double heightAmplitude;
private final String sourceName;
private final int caveHiddenCount;
private final double[][] caveHiddenWeights;
private final double[] caveHiddenBias;
private final double[] caveWeights;
private final double caveBias;
private JsonTerrainModel(JsonObject json, String sourceName) {
if (json.get("format").getAsInt() != 1) {
throw new IllegalArgumentException("unsupported model format");
}
inputCount = json.get("inputs").getAsInt();
hiddenCount = json.get("hidden").getAsInt();
if (inputCount != 10 || hiddenCount < 1 || hiddenCount > 256) {
throw new IllegalArgumentException("expected 10 inputs and 1..256 hidden units");
}
hiddenWeights = matrix(json.getAsJsonArray("hidden_weights"), hiddenCount, inputCount);
hiddenBias = vector(json.getAsJsonArray("hidden_bias"), hiddenCount);
heightWeights = vector(json.getAsJsonArray("height_weights"), hiddenCount);
moistureWeights = vector(json.getAsJsonArray("moisture_weights"), hiddenCount);
temperatureWeights = vector(json.getAsJsonArray("temperature_weights"), hiddenCount);
heightBias = json.has("height_bias") ? json.get("height_bias").getAsDouble() : 0.0;
moistureBias = json.has("moisture_bias") ? json.get("moisture_bias").getAsDouble() : 0.0;
temperatureBias = json.has("temperature_bias") ? json.get("temperature_bias").getAsDouble() : 0.0;
baseHeight = json.has("base_height") ? json.get("base_height").getAsDouble() : 68.0;
heightAmplitude = json.has("height_amplitude") ? json.get("height_amplitude").getAsDouble() : 46.0;
boolean hasCaveNetwork = json.has("cave_hidden")
&& json.has("cave_hidden_weights")
&& json.has("cave_hidden_bias")
&& json.has("cave_weights");
if (hasCaveNetwork) {
caveHiddenCount = json.get("cave_hidden").getAsInt();
if (caveHiddenCount < 1 || caveHiddenCount > 128) {
throw new IllegalArgumentException("cave hidden units must be in 1..128");
}
caveHiddenWeights = matrix(json.getAsJsonArray("cave_hidden_weights"), caveHiddenCount, 8);
caveHiddenBias = vector(json.getAsJsonArray("cave_hidden_bias"), caveHiddenCount);
caveWeights = vector(json.getAsJsonArray("cave_weights"), caveHiddenCount);
caveBias = json.has("cave_bias") ? json.get("cave_bias").getAsDouble() : 0.0;
} else {
caveHiddenCount = 0;
caveHiddenWeights = null;
caveHiddenBias = null;
caveWeights = null;
caveBias = 0.0;
}
this.sourceName = sourceName;
}
public static JsonTerrainModel load(Path path) throws Exception {
String text = Files.readString(path, StandardCharsets.UTF_8);
return new JsonTerrainModel(JsonParser.parseString(text).getAsJsonObject(), path.getFileName().toString());
}
@Override
public TerrainPrediction predict(long seed, int worldX, int worldZ, int minY, int maxY) {
double[] input = EmbeddedTerrainModel.features(seed, worldX, worldZ);
double[] hidden = new double[hiddenCount];
for (int h = 0; h < hiddenCount; h++) {
double sum = hiddenBias[h];
for (int i = 0; i < inputCount; i++) {
sum += hiddenWeights[h][i] * input[i];
}
hidden[h] = Math.tanh(sum);
}
double height = Math.tanh(dot(hidden, heightWeights) + heightBias);
double moisture = EmbeddedTerrainModel.sigmoid(dot(hidden, moistureWeights) + moistureBias);
double temperature = EmbeddedTerrainModel.sigmoid(dot(hidden, temperatureWeights) + temperatureBias);
return EmbeddedTerrainModel.toPrediction(height, moisture, temperature, minY, maxY, baseHeight, heightAmplitude);
}
@Override
public float caveProbability(long seed, int worldX, int y, int worldZ) {
if (caveHiddenCount == 0) {
return EMBEDDED_CAVE_FALLBACK.caveProbability(seed, worldX, y, worldZ);
}
double[] input = EmbeddedTerrainModel.caveFeatures(seed, worldX, y, worldZ);
double[] hidden = new double[caveHiddenCount];
for (int h = 0; h < caveHiddenCount; h++) {
double sum = caveHiddenBias[h];
for (int i = 0; i < input.length; i++) {
sum += caveHiddenWeights[h][i] * input[i];
}
hidden[h] = Math.tanh(sum);
}
return (float) EmbeddedTerrainModel.sigmoid(2.0 * dot(hidden, caveWeights) + caveBias);
}
@Override
public String name() {
return "json:" + sourceName;
}
private static double dot(double[] left, double[] right) {
double sum = 0.0;
for (int i = 0; i < left.length; i++) {
sum += left[i] * right[i];
}
return sum;
}
private static double[] vector(JsonArray array, int expected) {
if (array.size() != expected) {
throw new IllegalArgumentException("vector length mismatch");
}
double[] result = new double[expected];
for (int i = 0; i < expected; i++) {
result[i] = array.get(i).getAsDouble();
}
return result;
}
private static double[][] matrix(JsonArray array, int rows, int columns) {
if (array.size() != rows) {
throw new IllegalArgumentException("matrix row count mismatch");
}
double[][] result = new double[rows][columns];
for (int row = 0; row < rows; row++) {
JsonArray values = array.get(row).getAsJsonArray();
if (values.size() != columns) {
throw new IllegalArgumentException("matrix column count mismatch");
}
for (int column = 0; column < columns; column++) {
result[row][column] = values.get(column).getAsDouble();
}
}
return result;
}
}
@@ -0,0 +1,47 @@
package com.neuralterrain.model;
import com.neuralterrain.NeuralTerrainMod;
import java.nio.file.Files;
import java.nio.file.Path;
/** Selects the external JSON model when available and otherwise uses the embedded MLP. */
public final class NeuralTerrainModel {
private static volatile TerrainModelBackend active = new EmbeddedTerrainModel();
private static final TerrainPredictionCache CACHE = new TerrainPredictionCache();
private NeuralTerrainModel() {
}
public static void load(Path modelPath) {
CACHE.clear();
if (modelPath != null && Files.isRegularFile(modelPath)) {
try {
active = JsonTerrainModel.load(modelPath);
NeuralTerrainMod.LOGGER.info("Loaded terrain model backend {}", active.name());
return;
} catch (Exception exception) {
NeuralTerrainMod.LOGGER.error("Could not load terrain model {}, using embedded fallback", modelPath, exception);
}
}
active = new EmbeddedTerrainModel();
NeuralTerrainMod.LOGGER.info("Using terrain model backend {}", active.name());
}
public static TerrainPrediction predict(long seed, int worldX, int worldZ, int minY, int maxY) {
TerrainPrediction cached = CACHE.get(seed, worldX, worldZ, minY, maxY);
if (cached != null) {
return cached;
}
TerrainPrediction prediction = active.predict(seed, worldX, worldZ, minY, maxY);
CACHE.put(seed, worldX, worldZ, minY, maxY, prediction);
return prediction;
}
public static float caveProbability(long seed, int worldX, int y, int worldZ) {
return active.caveProbability(seed, worldX, y, worldZ);
}
public static String backendName() {
return active.name();
}
}
@@ -0,0 +1,12 @@
package com.neuralterrain.model;
/** Runtime contract shared by the embedded and external neural networks. */
public interface TerrainModelBackend {
TerrainPrediction predict(long seed, int worldX, int worldZ, int minY, int maxY);
default float caveProbability(long seed, int worldX, int y, int worldZ) {
return 0.0f;
}
String name();
}
@@ -0,0 +1,5 @@
package com.neuralterrain.model;
/** Output of the terrain network for one world position. */
public record TerrainPrediction(int surfaceY, float moisture, float temperature) {
}
@@ -0,0 +1,39 @@
package com.neuralterrain.model;
import java.util.LinkedHashMap;
import java.util.Map;
/** Small bounded cache shared by the multiple Minecraft generation stages. */
final class TerrainPredictionCache {
private static final int MAX_ENTRIES = 32_768;
private final Map<Long, TerrainPrediction> entries = new LinkedHashMap<>(1024, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Long, TerrainPrediction> eldest) {
return size() > MAX_ENTRIES;
}
};
synchronized TerrainPrediction get(long seed, int worldX, int worldZ, int minY, int maxY) {
return entries.get(key(seed, worldX, worldZ, minY, maxY));
}
synchronized void put(long seed, int worldX, int worldZ, int minY, int maxY, TerrainPrediction prediction) {
entries.put(key(seed, worldX, worldZ, minY, maxY), prediction);
}
synchronized void clear() {
entries.clear();
}
private static long key(long seed, int worldX, int worldZ, int minY, int maxY) {
long value = seed;
value ^= (long) worldX * 0x9E3779B185EBCA87L;
value ^= (long) worldZ * 0xC2B2AE3D27D4EB4FL;
value ^= (long) minY * 0x165667B19E3779F9L;
value ^= (long) maxY * 0x85EBCA77C2B2AE63L;
value ^= value >>> 33;
value *= 0xff51afd7ed558ccdL;
value ^= value >>> 33;
return value;
}
}
@@ -0,0 +1,62 @@
package com.neuralterrain.tools;
import com.google.gson.JsonObject;
import com.neuralterrain.config.NeuralTerrainConfig;
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.Heightmap;
/** Exports interior air labels from an existing world for training the cave head. */
public final class CaveSampleExporter {
private CaveSampleExporter() {
}
public static ExportResult export(ServerLevel level, BlockPos center, int radius, int stride, int minY, int maxY) throws Exception {
Path path = NeuralTerrainConfig.caveSamplesPath();
Files.createDirectories(path.getParent());
long seed = level.getSeed();
int worldMinY = Math.max(level.getMinBuildHeight() + NeuralTerrainConfig.bedrockLayers() + 5, minY);
int worldMaxY = Math.min(level.getMaxBuildHeight() - 1, maxY);
int samples = 0;
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
for (int x = center.getX() - radius; x <= center.getX() + radius; x += stride) {
for (int z = center.getZ() - radius; z <= center.getZ() + radius; z += stride) {
int surface = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z);
int top = Math.min(worldMaxY, surface - 6);
for (int y = worldMinY; y <= top; y += stride) {
BlockPos pos = new BlockPos(x, y, z);
if (level.getBlockState(pos).is(Blocks.BEDROCK)) {
continue;
}
int solidNeighbors = 0;
for (BlockPos neighbor : new BlockPos[] {pos.north(), pos.south(), pos.east(), pos.west(), pos.above(), pos.below()}) {
if (!level.getBlockState(neighbor).isAir()) {
solidNeighbors++;
}
}
if (level.getBlockState(pos).isAir() || solidNeighbors >= 4) {
JsonObject row = new JsonObject();
row.addProperty("seed", seed);
row.addProperty("x", x);
row.addProperty("y", y);
row.addProperty("z", z);
row.addProperty("cave", level.getBlockState(pos).isAir() ? 1.0 : 0.0);
writer.write(row.toString());
writer.newLine();
samples++;
}
}
}
}
}
return new ExportResult(path, samples);
}
public record ExportResult(Path path, int samples) {
}
}
@@ -0,0 +1,69 @@
package com.neuralterrain.tools;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.neuralterrain.config.NeuralTerrainConfig;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.levelgen.Heightmap;
/** Exports real generated columns as JSONL training samples. */
public final class TerrainSampleExporter {
private static final Gson GSON = new GsonBuilder().create();
private TerrainSampleExporter() {
}
public static ExportResult export(ServerLevel level, BlockPos center, int radius, int stride) throws IOException {
Path output = NeuralTerrainConfig.samplesPath();
Files.createDirectories(output.getParent());
int minX = center.getX() - radius;
int maxX = center.getX() + radius;
int minZ = center.getZ() - radius;
int maxZ = center.getZ() + radius;
int count = 0;
try (BufferedWriter writer = Files.newBufferedWriter(
output,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE
)) {
for (int x = minX; x <= maxX; x += stride) {
for (int z = minZ; z <= maxZ; z += stride) {
int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z);
int surfaceY = Math.max(level.getMinBuildHeight() + 1, height - 1);
BlockPos position = new BlockPos(x, surfaceY, z);
Biome biome = level.getBiome(position).value();
JsonObject sample = new JsonObject();
sample.addProperty("seed", level.getSeed());
sample.addProperty("x", x);
sample.addProperty("z", z);
sample.addProperty("surface_y", surfaceY);
sample.addProperty("moisture", biome.hasPrecipitation() ? 0.7 : 0.1);
sample.addProperty("temperature", normalizeTemperature(biome.getBaseTemperature()));
writer.write(GSON.toJson(sample));
writer.newLine();
count++;
}
}
}
return new ExportResult(output, count);
}
private static double normalizeTemperature(float temperature) {
return Math.max(0.0, Math.min(1.0, (temperature + 1.0) / 3.0));
}
public record ExportResult(Path path, int samples) {
}
}
@@ -0,0 +1,212 @@
package com.neuralterrain.world;
import com.neuralterrain.config.NeuralTerrainConfig;
import com.neuralterrain.model.NeuralTerrainModel;
import com.neuralterrain.model.TerrainPrediction;
import java.util.Random;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.Heightmap;
/** Model-driven post-decoration that complements the normal biome feature pipeline. */
public final class NeuralDecoration {
private static final BlockState STONE = Blocks.STONE.defaultBlockState();
private static final BlockState DEEPSLATE = Blocks.DEEPSLATE.defaultBlockState();
private static final BlockState GRASS_BLOCK = Blocks.GRASS_BLOCK.defaultBlockState();
private static final BlockState DIRT = Blocks.DIRT.defaultBlockState();
private static final BlockState SAND = Blocks.SAND.defaultBlockState();
private static final BlockState OAK_LOG = Blocks.OAK_LOG.defaultBlockState();
private static final BlockState OAK_LEAVES = Blocks.OAK_LEAVES.defaultBlockState();
private static final BlockState GRASS = Blocks.GRASS.defaultBlockState();
private static final BlockState DANDELION = Blocks.DANDELION.defaultBlockState();
private static final BlockState POPPY = Blocks.POPPY.defaultBlockState();
private NeuralDecoration() {
}
public static void decorate(WorldGenLevel level, ChunkAccess chunk, long seed) {
if (!NeuralTerrainConfig.enabled() || !NeuralTerrainConfig.generateNeuralDecorations()) {
return;
}
long decorationSeed = mix(seed, chunk.getPos().x, chunk.getPos().z);
Random random = new Random(decorationSeed);
if (NeuralTerrainConfig.generateNeuralOres()) {
decorateOres(level, chunk, seed, random);
}
if (NeuralTerrainConfig.generateNeuralVegetation()) {
decorateVegetation(level, chunk, seed, random);
}
if (NeuralTerrainConfig.generateNeuralStructures()) {
decorateRuin(level, chunk, seed, random);
}
}
private static void decorateOres(WorldGenLevel level, ChunkAccess chunk, long seed, Random random) {
int minY = level.getMinBuildHeight();
int maxY = level.getMaxBuildHeight();
int startX = chunk.getPos().getMinBlockX();
int startZ = chunk.getPos().getMinBlockZ();
TerrainPrediction center = NeuralTerrainModel.predict(seed, startX + 8, startZ + 8, minY, maxY);
int extraTries = 2 + Math.round(center.moisture() * 3.0f);
for (int i = 0; i < extraTries; i++) {
placeVein(level, random, startX, startZ, minY, maxY, Blocks.COAL_ORE.defaultBlockState(), -16, 96, 8);
placeVein(level, random, startX, startZ, minY, maxY, Blocks.IRON_ORE.defaultBlockState(), -32, 64, 6);
}
for (int i = 0; i < 1 + Math.round(center.temperature()); i++) {
placeVein(level, random, startX, startZ, minY, maxY, Blocks.COPPER_ORE.defaultBlockState(), -16, 56, 7);
placeVein(level, random, startX, startZ, minY, maxY, Blocks.GOLD_ORE.defaultBlockState(), -32, 32, 5);
}
placeVein(level, random, startX, startZ, minY, maxY, Blocks.REDSTONE_ORE.defaultBlockState(), -64, 16, 5);
placeVein(level, random, startX, startZ, minY, maxY, Blocks.LAPIS_ORE.defaultBlockState(), -32, 32, 4);
if (center.moisture() > 0.38f) {
placeVein(level, random, startX, startZ, minY, maxY, Blocks.DIAMOND_ORE.defaultBlockState(), -64, 12, 2);
}
}
private static void placeVein(
WorldGenLevel level,
Random random,
int startX,
int startZ,
int minY,
int maxY,
BlockState ore,
int minOreY,
int maxOreY,
int size
) {
int x = startX + random.nextInt(16);
int z = startZ + random.nextInt(16);
int y = minOreY + random.nextInt(Math.max(1, maxOreY - minOreY + 1));
if (y < minY || y >= maxY) {
return;
}
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(x, y, z);
for (int i = 0; i < size; i++) {
pos.set(
Math.max(startX, Math.min(startX + 15, x + random.nextInt(5) - 2)),
Math.max(minY, Math.min(maxY - 1, y + random.nextInt(5) - 2)),
Math.max(startZ, Math.min(startZ + 15, z + random.nextInt(5) - 2))
);
BlockState current = level.getBlockState(pos);
if (current.is(STONE.getBlock()) || current.is(DEEPSLATE.getBlock())) {
level.setBlock(pos, current.is(DEEPSLATE.getBlock()) ? deepslateVariant(ore) : ore, 2);
}
}
}
private static BlockState deepslateVariant(BlockState ore) {
if (ore.is(Blocks.COAL_ORE)) return Blocks.DEEPSLATE_COAL_ORE.defaultBlockState();
if (ore.is(Blocks.IRON_ORE)) return Blocks.DEEPSLATE_IRON_ORE.defaultBlockState();
if (ore.is(Blocks.COPPER_ORE)) return Blocks.DEEPSLATE_COPPER_ORE.defaultBlockState();
if (ore.is(Blocks.GOLD_ORE)) return Blocks.DEEPSLATE_GOLD_ORE.defaultBlockState();
if (ore.is(Blocks.REDSTONE_ORE)) return Blocks.DEEPSLATE_REDSTONE_ORE.defaultBlockState();
if (ore.is(Blocks.LAPIS_ORE)) return Blocks.DEEPSLATE_LAPIS_ORE.defaultBlockState();
if (ore.is(Blocks.DIAMOND_ORE)) return Blocks.DEEPSLATE_DIAMOND_ORE.defaultBlockState();
return ore;
}
private static void decorateVegetation(WorldGenLevel level, ChunkAccess chunk, long seed, Random random) {
int minY = level.getMinBuildHeight();
int maxY = level.getMaxBuildHeight();
int startX = chunk.getPos().getMinBlockX();
int startZ = chunk.getPos().getMinBlockZ();
for (int attempt = 0; attempt < 10; attempt++) {
int x = startX + 2 + random.nextInt(12);
int z = startZ + 2 + random.nextInt(12);
TerrainPrediction prediction = NeuralTerrainModel.predict(seed, x, z, minY, maxY);
int y = surfaceY(level, x, z);
if (y <= minY || y + 6 >= maxY) {
continue;
}
BlockPos ground = new BlockPos(x, y, z);
BlockState groundState = level.getBlockState(ground);
if (!groundState.is(GRASS_BLOCK.getBlock()) && !groundState.is(DIRT.getBlock())) {
continue;
}
if (prediction.temperature() > 0.78f && prediction.moisture() < 0.32f) {
continue;
}
if (prediction.moisture() > 0.48f && random.nextFloat() < 0.25f) {
placeTree(level, x, y + 1, z, maxY);
} else {
BlockPos plant = ground.above();
if (level.isEmptyBlock(plant)) {
level.setBlock(plant, random.nextBoolean() ? GRASS : (prediction.temperature() > 0.55f ? DANDELION : POPPY), 2);
}
}
}
}
private static void placeTree(WorldGenLevel level, int x, int y, int z, int maxY) {
for (int dy = 0; dy < 4; dy++) {
BlockPos trunk = new BlockPos(x, y + dy, z);
if (!level.isEmptyBlock(trunk)) {
return;
}
}
for (int dy = 0; dy < 4; dy++) {
level.setBlock(new BlockPos(x, y + dy, z), OAK_LOG, 2);
}
for (int dx = -2; dx <= 2; dx++) {
for (int dz = -2; dz <= 2; dz++) {
for (int dy = 2; dy <= 4; dy++) {
if (Math.abs(dx) + Math.abs(dz) <= (dy == 4 ? 1 : 2)) {
BlockPos leaves = new BlockPos(x + dx, y + dy, z + dz);
if (leaves.getY() < maxY && level.isEmptyBlock(leaves)) {
level.setBlock(leaves, OAK_LEAVES, 2);
}
}
}
}
}
}
private static void decorateRuin(WorldGenLevel level, ChunkAccess chunk, long seed, Random random) {
int centerX = chunk.getPos().getMinBlockX() + 8;
int centerZ = chunk.getPos().getMinBlockZ() + 8;
int minY = level.getMinBuildHeight();
int maxY = level.getMaxBuildHeight();
TerrainPrediction prediction = NeuralTerrainModel.predict(seed, centerX, centerZ, minY, maxY);
if (prediction.surfaceY() < NeuralTerrainConfig.seaLevel()
|| prediction.moisture() > 0.78f
|| random.nextFloat() > 0.07f) {
return;
}
int groundY = surfaceY(level, centerX, centerZ);
if (groundY <= minY + 1 || groundY + 5 >= maxY) {
return;
}
for (int dx = -2; dx <= 2; dx++) {
for (int dz = -2; dz <= 2; dz++) {
BlockPos floor = new BlockPos(centerX + dx, groundY, centerZ + dz);
level.setBlock(floor, (Math.abs(dx) + Math.abs(dz)) % 2 == 0 ? Blocks.STONE_BRICKS.defaultBlockState() : Blocks.MOSSY_STONE_BRICKS.defaultBlockState(), 2);
if (Math.abs(dx) == 2 || Math.abs(dz) == 2) {
level.setBlock(floor.above(), Blocks.STONE_BRICKS.defaultBlockState(), 2);
if ((dx + dz) % 2 == 0) {
level.setBlock(floor.above(2), Blocks.COBBLESTONE.defaultBlockState(), 2);
}
}
}
}
}
private static int surfaceY(WorldGenLevel level, int x, int z) {
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z);
while (y > level.getMinBuildHeight() && level.isEmptyBlock(new BlockPos(x, y, z))) {
y--;
}
return y;
}
private static long mix(long seed, int chunkX, int chunkZ) {
long value = seed + chunkX * 341873128712L + chunkZ * 132897987541L;
value ^= value >>> 33;
value *= 0xff51afd7ed558ccdL;
value ^= value >>> 33;
return value;
}
}
@@ -0,0 +1,45 @@
package com.neuralterrain.world;
import com.neuralterrain.config.NeuralTerrainConfig;
import com.neuralterrain.model.TerrainPrediction;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
/** Maps the neural climate head to a vanilla biome so later generation stages stay coherent. */
public final class NeuralTerrainBiomes {
private NeuralTerrainBiomes() {
}
public static Holder<Biome> select(Registry<Biome> registry, TerrainPrediction prediction) {
if (prediction.surfaceY() < NeuralTerrainConfig.seaLevel()) {
return holder(registry, Biomes.OCEAN);
}
float moisture = prediction.moisture();
float temperature = prediction.temperature();
if (temperature < 0.24f) {
return holder(registry, moisture > 0.55f ? Biomes.SNOWY_TAIGA : Biomes.SNOWY_PLAINS);
}
if (temperature > 0.78f && moisture < 0.32f) {
return holder(registry, Biomes.DESERT);
}
if (temperature > 0.68f && moisture < 0.48f) {
return holder(registry, Biomes.SAVANNA);
}
if (temperature > 0.72f && moisture > 0.68f) {
return holder(registry, Biomes.JUNGLE);
}
if (moisture > 0.78f) {
return holder(registry, Biomes.SWAMP);
}
if (moisture > 0.56f) {
return holder(registry, temperature < 0.45f ? Biomes.TAIGA : Biomes.FOREST);
}
return holder(registry, Biomes.PLAINS);
}
private static Holder<Biome> holder(Registry<Biome> registry, net.minecraft.resources.ResourceKey<Biome> key) {
return registry.getHolderOrThrow(key);
}
}
@@ -0,0 +1,22 @@
package com.neuralterrain.world;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.world.level.chunk.ChunkGenerator;
/** Associates a live server world's seed with its generator instance. */
public final class NeuralTerrainContext {
private static final Map<ChunkGenerator, Long> SEEDS = Collections.synchronizedMap(new WeakHashMap<>());
private NeuralTerrainContext() {
}
public static void register(ChunkGenerator generator, long seed) {
SEEDS.put(generator, seed);
}
public static long seedFor(ChunkGenerator generator) {
return SEEDS.getOrDefault(generator, 0L);
}
}
@@ -0,0 +1,79 @@
package com.neuralterrain.world;
import com.neuralterrain.config.NeuralTerrainConfig;
import com.neuralterrain.model.NeuralTerrainModel;
import com.neuralterrain.model.TerrainPrediction;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.Heightmap;
/** Converts the network's continuous prediction into Minecraft block columns. */
public final class NeuralTerrainRewriter {
private static final BlockState STONE = Blocks.STONE.defaultBlockState();
private static final BlockState DEEPSLATE = Blocks.DEEPSLATE.defaultBlockState();
private static final BlockState DIRT = Blocks.DIRT.defaultBlockState();
private static final BlockState GRASS = Blocks.GRASS_BLOCK.defaultBlockState();
private static final BlockState SAND = Blocks.SAND.defaultBlockState();
private static final BlockState WATER = Blocks.WATER.defaultBlockState();
private static final BlockState AIR = Blocks.AIR.defaultBlockState();
private static final BlockState BEDROCK = Blocks.BEDROCK.defaultBlockState();
private NeuralTerrainRewriter() {
}
public static ChunkAccess rewrite(ChunkAccess chunk, long seed) {
int minY = chunk.getMinBuildHeight();
int maxY = minY + chunk.getHeight();
int startX = chunk.getPos().getMinBlockX();
int startZ = chunk.getPos().getMinBlockZ();
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int localX = 0; localX < 16; localX++) {
for (int localZ = 0; localZ < 16; localZ++) {
int worldX = startX + localX;
int worldZ = startZ + localZ;
TerrainPrediction prediction = NeuralTerrainModel.predict(seed, worldX, worldZ, minY, maxY);
int surfaceY = prediction.surfaceY();
int seaLevel = NeuralTerrainConfig.seaLevel();
boolean beach = surfaceY <= seaLevel + NeuralTerrainConfig.beachMargin();
boolean dry = prediction.temperature() > 0.72f && prediction.moisture() < 0.42f;
BlockState surfaceState = beach || dry ? SAND : GRASS;
int columnTop = NeuralTerrainConfig.generateWater() && surfaceY < seaLevel ? seaLevel : surfaceY;
columnTop = Math.min(maxY - 1, columnTop);
for (int y = minY; y <= columnTop; y++) {
BlockState state;
if (y < minY + NeuralTerrainConfig.bedrockLayers()) {
state = BEDROCK;
} else if (y < surfaceY - 3) {
boolean cave = NeuralTerrainConfig.generateNeuralCaves()
&& y > minY + NeuralTerrainConfig.bedrockLayers() + 4
&& y < surfaceY - 6
&& NeuralTerrainModel.caveProbability(seed, worldX, y, worldZ)
>= NeuralTerrainConfig.caveThreshold();
state = cave ? AIR : (y < 0 ? DEEPSLATE : STONE);
} else if (y < surfaceY) {
state = beach || dry ? SAND : DIRT;
} else if (y == surfaceY) {
state = surfaceState;
} else if (NeuralTerrainConfig.generateWater() && y <= seaLevel && surfaceY < seaLevel) {
state = WATER;
} else {
state = AIR;
}
mutable.set(worldX, y, worldZ);
chunk.setBlockState(mutable, state, false);
}
// Keep heightmaps coherent for later surface/decoration stages.
int heightmapY = columnTop;
BlockState heightmapState = surfaceY < seaLevel && NeuralTerrainConfig.generateWater() ? WATER : surfaceState;
chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG)
.update(localX, heightmapY, localZ, heightmapState);
}
}
return chunk;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

+26
View File
@@ -0,0 +1,26 @@
{
"schemaVersion": 1,
"id": "neuralterrain",
"version": "${version}",
"name": "Neural Terrain",
"description": "A Fabric 1.20.1 terrain generator driven by a deterministic neural terrain model.",
"authors": [
"Neural Terrain Team"
],
"license": "CC0-1.0",
"environment": "*",
"entrypoints": {
"main": [
"com.neuralterrain.NeuralTerrainMod"
]
},
"mixins": [
"neuralterrain.mixins.json"
],
"depends": {
"fabricloader": ">=0.16.10",
"minecraft": "1.20.1",
"java": ">=17",
"fabric-api": "*"
}
}
@@ -0,0 +1,17 @@
{
"required": true,
"package": "com.neuralterrain.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"ChunkGeneratorMixin",
"NoiseBasedChunkGeneratorMixin",
"ServerLevelMixin"
],
"client": [
"RenderSystemDevMixin",
"WindowDevMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -0,0 +1,63 @@
package com.neuralterrain;
import com.mojang.serialization.Lifecycle;
import com.neuralterrain.model.TerrainPrediction;
import com.neuralterrain.world.NeuralTerrainBiomes;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.Bootstrap;
import net.minecraft.SharedConstants;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeSpecialEffects;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.MobSpawnSettings;
import java.util.List;
import net.minecraft.resources.ResourceKey;
/** Verifies that model climate signals map to the intended vanilla biome keys. */
public final class BiomeSelectionSmoke {
public static void main(String[] args) {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
MappedRegistry<Biome> registry = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
for (ResourceKey<Biome> key : List.of(
Biomes.OCEAN, Biomes.SNOWY_TAIGA, Biomes.SNOWY_PLAINS, Biomes.DESERT,
Biomes.SAVANNA, Biomes.JUNGLE, Biomes.SWAMP, Biomes.TAIGA,
Biomes.FOREST, Biomes.PLAINS
)) {
Registry.register(registry, key, biome());
}
assertKey(registry, new TerrainPrediction(40, 0.5f, 0.5f), Biomes.OCEAN);
assertKey(registry, new TerrainPrediction(80, 0.7f, 0.1f), Biomes.SNOWY_TAIGA);
assertKey(registry, new TerrainPrediction(80, 0.1f, 0.9f), Biomes.DESERT);
assertKey(registry, new TerrainPrediction(80, 0.8f, 0.75f), Biomes.JUNGLE);
assertKey(registry, new TerrainPrediction(80, 0.4f, 0.5f), Biomes.PLAINS);
System.out.println("biome selection smoke ok");
}
private static void assertKey(Registry<Biome> registry, TerrainPrediction prediction, net.minecraft.resources.ResourceKey<Biome> expected) {
Holder<Biome> actual = NeuralTerrainBiomes.select(registry, prediction);
if (actual.unwrapKey().isEmpty() || !actual.unwrapKey().get().equals(expected)) {
throw new AssertionError("expected " + expected + " but got " + actual);
}
}
private static Biome biome() {
return new Biome.BiomeBuilder()
.hasPrecipitation(true)
.temperature(0.8f)
.downfall(0.4f)
.specialEffects(new BiomeSpecialEffects.Builder()
.fogColor(12638463)
.waterColor(4159204)
.waterFogColor(329011)
.skyColor(7907327)
.build())
.mobSpawnSettings(MobSpawnSettings.EMPTY)
.generationSettings(BiomeGenerationSettings.EMPTY)
.build();
}
}
@@ -0,0 +1,105 @@
package com.neuralterrain;
import com.neuralterrain.model.NeuralTerrainModel;
import com.neuralterrain.model.TerrainPrediction;
import com.neuralterrain.world.NeuralTerrainRewriter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import com.mojang.serialization.Lifecycle;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.server.Bootstrap;
import net.minecraft.SharedConstants;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeSpecialEffects;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.MobSpawnSettings;
/** Headless generation test that does not require accepting the Minecraft EULA. */
public final class ChunkGenerationSmoke {
private static final long SEED = 123456789L;
public static void main(String[] args) {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
LevelHeightAccessor height = LevelHeightAccessor.create(-64, 384);
ProtoChunk first = newChunk(height);
ProtoChunk second = newChunk(height);
NeuralTerrainRewriter.rewrite(first, SEED);
NeuralTerrainRewriter.rewrite(second, SEED);
TerrainPrediction prediction = NeuralTerrainModel.predict(SEED, 0, 0, -64, 320);
BlockPos base = new BlockPos(0, -64, 0);
BlockPos surface = new BlockPos(0, prediction.surfaceY(), 0);
if (!first.getBlockState(base).is(Blocks.BEDROCK)) {
throw new AssertionError("bottom layer is not bedrock");
}
if (!first.getBlockState(surface).is(Blocks.GRASS_BLOCK)
&& !first.getBlockState(surface).is(Blocks.SAND)) {
throw new AssertionError("predicted surface is not a terrain block: " + first.getBlockState(surface));
}
if (first.getBlockState(surface).getBlock() != second.getBlockState(surface).getBlock()) {
throw new AssertionError("same seed and coordinates produced different terrain");
}
int heightmap = first.getHeight(Heightmap.Types.WORLD_SURFACE_WG, 0, 0);
if (heightmap <= -64) {
throw new AssertionError("heightmap was not updated");
}
float caveA = NeuralTerrainModel.caveProbability(SEED, 3, 20, 7);
float caveB = NeuralTerrainModel.caveProbability(SEED, 3, 20, 7);
if (caveA != caveB || caveA < 0.0f || caveA > 1.0f) {
throw new AssertionError("cave network is not deterministic or bounded");
}
int caveAir = 0;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
TerrainPrediction column = NeuralTerrainModel.predict(SEED, x, z, -64, 320);
for (int y = -64 + 7; y < column.surfaceY() - 6; y++) {
if (first.getBlockState(new BlockPos(x, y, z)).isAir()) {
caveAir++;
}
}
}
}
if (caveAir == 0) {
throw new AssertionError("cave network did not carve any deep air blocks");
}
System.out.println("chunk smoke ok: surface=" + prediction.surfaceY() + ", heightmap=" + heightmap + ", cave_air=" + caveAir);
}
private static ProtoChunk newChunk(LevelHeightAccessor height) {
return new ProtoChunk(
new ChunkPos(0, 0),
UpgradeData.EMPTY,
height,
biomeRegistry(),
null
);
}
private static Registry<Biome> biomeRegistry() {
MappedRegistry<Biome> registry = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
Biome biome = new Biome.BiomeBuilder()
.hasPrecipitation(true)
.temperature(0.8f)
.downfall(0.4f)
.specialEffects(new BiomeSpecialEffects.Builder()
.fogColor(12638463)
.waterColor(4159204)
.waterFogColor(329011)
.skyColor(7907327)
.build())
.mobSpawnSettings(MobSpawnSettings.EMPTY)
.generationSettings(BiomeGenerationSettings.EMPTY)
.build();
Registry.register(registry, Biomes.PLAINS, biome);
return registry;
}
}
@@ -0,0 +1,115 @@
package com.neuralterrain;
import com.mojang.serialization.Lifecycle;
import com.neuralterrain.world.NeuralDecoration;
import com.neuralterrain.world.NeuralTerrainRewriter;
import java.lang.reflect.Proxy;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeSpecialEffects;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.Heightmap;
/** Executes the custom decoration pass against a real ProtoChunk without a client or EULA. */
public final class DecorationSmoke {
private static final long SEED = 123456789L;
public static void main(String[] args) {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
LevelHeightAccessor height = LevelHeightAccessor.create(-64, 384);
ProtoChunk chunk = new ProtoChunk(new ChunkPos(0, 0), UpgradeData.EMPTY, height, biomeRegistry(), null);
NeuralTerrainRewriter.rewrite(chunk, SEED);
WorldGenLevel level = worldGenLevel(chunk, height);
NeuralDecoration.decorate(level, chunk, SEED);
int oreBlocks = 0;
int vegetationBlocks = 0;
int ruinBlocks = 0;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = -64; y < 384; y++) {
var block = chunk.getBlockState(new BlockPos(x, y, z)).getBlock();
if (block == Blocks.COAL_ORE || block == Blocks.IRON_ORE || block == Blocks.COPPER_ORE
|| block == Blocks.GOLD_ORE || block == Blocks.REDSTONE_ORE || block == Blocks.LAPIS_ORE
|| block == Blocks.DIAMOND_ORE || block == Blocks.DEEPSLATE_COAL_ORE
|| block == Blocks.DEEPSLATE_IRON_ORE || block == Blocks.DEEPSLATE_COPPER_ORE
|| block == Blocks.DEEPSLATE_GOLD_ORE || block == Blocks.DEEPSLATE_REDSTONE_ORE
|| block == Blocks.DEEPSLATE_LAPIS_ORE || block == Blocks.DEEPSLATE_DIAMOND_ORE) {
oreBlocks++;
}
if (block == Blocks.OAK_LOG || block == Blocks.OAK_LEAVES || block == Blocks.GRASS
|| block == Blocks.DANDELION || block == Blocks.POPPY) {
vegetationBlocks++;
}
if (block == Blocks.STONE_BRICKS || block == Blocks.MOSSY_STONE_BRICKS) {
ruinBlocks++;
}
}
}
}
if (oreBlocks == 0 || vegetationBlocks == 0) {
throw new AssertionError("decoration pass placed no ores or vegetation: ores=" + oreBlocks + ", vegetation=" + vegetationBlocks);
}
System.out.println("decoration smoke ok: ores=" + oreBlocks + ", vegetation=" + vegetationBlocks + ", ruin=" + ruinBlocks);
}
private static WorldGenLevel worldGenLevel(ProtoChunk chunk, LevelHeightAccessor height) {
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(),
new Class<?>[] {WorldGenLevel.class},
(proxy, method, args) -> {
String name = method.getName();
if (name.equals("getSeed")) return SEED;
if (name.equals("getMinBuildHeight")) return height.getMinBuildHeight();
if (name.equals("getMaxBuildHeight")) return height.getMinBuildHeight() + height.getHeight();
if (name.equals("getBlockState")) return chunk.getBlockState((BlockPos) args[0]);
if (name.equals("isEmptyBlock")) return chunk.getBlockState((BlockPos) args[0]).isAir();
if (name.equals("setBlock")) {
chunk.setBlockState((BlockPos) args[0], (net.minecraft.world.level.block.state.BlockState) args[1], false);
return true;
}
if (name.equals("getHeight")) {
return chunk.getHeight((Heightmap.Types) args[0], (int) args[1], (int) args[2]);
}
if (method.getReturnType() == boolean.class) return false;
if (method.getReturnType() == int.class) return 0;
if (method.getReturnType() == long.class) return 0L;
return null;
}
);
}
private static Registry<Biome> biomeRegistry() {
MappedRegistry<Biome> registry = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
Biome biome = new Biome.BiomeBuilder()
.hasPrecipitation(true)
.temperature(0.8f)
.downfall(0.4f)
.specialEffects(new BiomeSpecialEffects.Builder()
.fogColor(12638463)
.waterColor(4159204)
.waterFogColor(329011)
.skyColor(7907327)
.build())
.mobSpawnSettings(MobSpawnSettings.EMPTY)
.generationSettings(BiomeGenerationSettings.EMPTY)
.build();
Registry.register(registry, Biomes.PLAINS, biome);
return registry;
}
}
@@ -0,0 +1,55 @@
package com.neuralterrain;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.neuralterrain.model.JsonTerrainModel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
/** Verifies the optional external cave-head JSON schema consumed by the runtime. */
public final class JsonModelSmoke {
public static void main(String[] args) throws Exception {
Path path = Files.createTempFile("neuralterrain-model-smoke", ".json");
try {
JsonObject json = new JsonObject();
json.addProperty("format", 1);
json.addProperty("inputs", 10);
json.addProperty("hidden", 1);
json.add("hidden_weights", matrix(1, 10));
json.add("hidden_bias", vector(1));
json.add("height_weights", vector(1));
json.add("moisture_weights", vector(1));
json.add("temperature_weights", vector(1));
json.addProperty("base_height", 68.0);
json.addProperty("height_amplitude", 46.0);
json.addProperty("cave_hidden", 1);
json.add("cave_hidden_weights", matrix(1, 8));
json.add("cave_hidden_bias", vector(1));
json.add("cave_weights", vector(1));
json.addProperty("cave_bias", 0.0);
Files.writeString(path, json.toString(), StandardCharsets.UTF_8);
JsonTerrainModel model = JsonTerrainModel.load(path);
float cave = model.caveProbability(123L, 4, 20, 8);
if (cave < 0.0f || cave > 1.0f) {
throw new AssertionError("external cave probability was out of range: " + cave);
}
System.out.println("json model smoke ok: cave=" + cave);
} finally {
Files.deleteIfExists(path);
}
}
private static JsonArray vector(int size) {
JsonArray array = new JsonArray();
for (int i = 0; i < size; i++) array.add(0.0);
return array;
}
private static JsonArray matrix(int rows, int columns) {
JsonArray array = new JsonArray();
for (int row = 0; row < rows; row++) array.add(vector(columns));
return array;
}
}
@@ -0,0 +1,91 @@
package com.neuralterrain;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import net.minecraft.server.Bootstrap;
import net.minecraft.SharedConstants;
/** Prints the mapped 1.20.1 generator hooks available to the integration layer. */
public final class WorldgenIntrospection {
private WorldgenIntrospection() {
}
public static void main(String[] args) throws Exception {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
Class<?> generator = Class.forName("net.minecraft.world.level.chunk.ChunkGenerator");
Arrays.stream(generator.getDeclaredMethods())
.filter(method -> method.getName().contains("Biome")
|| method.getName().contains("Structure")
|| method.getName().contains("Carver")
|| method.getName().contains("Noise")
|| method.getName().contains("Decoration")
|| method.getName().contains("Feature"))
.sorted((left, right) -> left.toString().compareTo(right.toString()))
.forEach(WorldgenIntrospection::print);
System.out.println("--- NoiseBasedChunkGenerator ---");
Class<?> noise = Class.forName("net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator");
System.out.println("--- NoiseBasedChunkGenerator constructors ---");
Arrays.stream(noise.getDeclaredConstructors())
.forEach(System.out::println);
System.out.println("--- registry access factories ---");
Class<?> access = Class.forName("net.minecraft.core.RegistryAccess");
Arrays.stream(access.getDeclaredMethods())
.filter(method -> method.getName().toLowerCase().contains("builtin")
|| method.getName().toLowerCase().contains("registry"))
.sorted((left, right) -> left.toString().compareTo(right.toString()))
.forEach(WorldgenIntrospection::print);
System.out.println("--- NoiseGeneratorSettings ---");
Class<?> settings = Class.forName("net.minecraft.world.level.levelgen.NoiseGeneratorSettings");
Arrays.stream(settings.getDeclaredConstructors()).forEach(System.out::println);
Arrays.stream(settings.getDeclaredMethods())
.filter(method -> Modifier.isStatic(method.getModifiers()) || method.getName().toLowerCase().contains("overworld"))
.sorted((left, right) -> left.toString().compareTo(right.toString()))
.forEach(WorldgenIntrospection::print);
System.out.println("--- BiomeSource implementations ---");
Class<?> fixedBiome = Class.forName("net.minecraft.world.level.biome.FixedBiomeSource");
Arrays.stream(fixedBiome.getDeclaredConstructors()).forEach(System.out::println);
System.out.println("--- StructureManager constructors ---");
Class<?> structureManager = Class.forName("net.minecraft.world.level.StructureManager");
Arrays.stream(structureManager.getDeclaredConstructors()).forEach(System.out::println);
Class<?> worldGenLevel = Class.forName("net.minecraft.world.level.WorldGenLevel");
System.out.println("WorldGenLevel interface=" + worldGenLevel.isInterface());
System.out.println("--- WorldOptions constructors ---");
Class<?> worldOptions = Class.forName("net.minecraft.world.level.levelgen.WorldOptions");
Arrays.stream(worldOptions.getDeclaredConstructors()).forEach(System.out::println);
System.out.println("--- StructureCheck constructors ---");
Class<?> structureCheck = Class.forName("net.minecraft.world.level.chunk.StructureCheck");
Arrays.stream(structureCheck.getDeclaredConstructors()).forEach(System.out::println);
Arrays.stream(noise.getDeclaredMethods())
.filter(method -> method.getName().contains("Surface")
|| method.getName().contains("Carver")
|| method.getName().contains("Noise")
|| method.getName().contains("Biome"))
.sorted((left, right) -> left.toString().compareTo(right.toString()))
.forEach(WorldgenIntrospection::print);
System.out.println("--- ChunkAccess biome/block hooks ---");
Class<?> chunk = Class.forName("net.minecraft.world.level.chunk.ChunkAccess");
Arrays.stream(chunk.getDeclaredMethods())
.filter(method -> method.getName().contains("Biome")
|| method.getName().contains("Block")
|| method.getName().contains("Height"))
.sorted((left, right) -> left.toString().compareTo(right.toString()))
.forEach(WorldgenIntrospection::print);
System.out.println("--- BuiltInRegistries fields ---");
Class<?> builtIns = Class.forName("net.minecraft.core.registries.BuiltInRegistries");
Arrays.stream(builtIns.getDeclaredFields())
.filter(field -> field.getType().getName().contains("Registry"))
.sorted((left, right) -> left.getName().compareTo(right.getName()))
.forEach(WorldgenIntrospection::printField);
}
private static void print(Method method) {
System.out.println(Modifier.toString(method.getModifiers()) + " " + method);
}
private static void printField(Field field) {
System.out.println(Modifier.toString(field.getModifiers()) + " " + field.getType().getName() + " " + field.getName());
}
}
+7
View File
@@ -0,0 +1,7 @@
import com.neuralterrain.model.JsonTerrainModel;
import java.nio.file.Path;
var model = JsonTerrainModel.load(Path.of("run/config/neuralterrain-model.json"));
System.out.println(model.name());
System.out.println(model.predict(123456789L, 0, 0, -64, 320));
System.out.println(model.predict(987654321L, 512, 512, -64, 320));
/exit
+5
View File
@@ -0,0 +1,5 @@
import com.neuralterrain.model.NeuralTerrainModel;
System.out.println(NeuralTerrainModel.predict(123456789L, 0, 0, -64, 320));
System.out.println(NeuralTerrainModel.predict(123456789L, 1024, 1024, -64, 320));
System.out.println(NeuralTerrainModel.predict(987654321L, 0, 0, -64, 320));
/exit
+8
View File
@@ -0,0 +1,8 @@
{"seed":123456789,"x":0,"y":20,"z":0,"cave":0.0}
{"seed":123456789,"x":4,"y":20,"z":0,"cave":1.0}
{"seed":123456789,"x":0,"y":32,"z":4,"cave":0.0}
{"seed":123456789,"x":4,"y":32,"z":4,"cave":1.0}
{"seed":987654321,"x":128,"y":-12,"z":64,"cave":0.0}
{"seed":987654321,"x":132,"y":-12,"z":64,"cave":1.0}
{"seed":987654321,"x":128,"y":8,"z":68,"cave":0.0}
{"seed":987654321,"x":132,"y":8,"z":68,"cave":1.0}
+8
View File
@@ -0,0 +1,8 @@
{"seed": 123456789, "x": 0, "z": 0, "surface_y": 72, "moisture": 0.70, "temperature": 0.55}
{"seed": 123456789, "x": 256, "z": 0, "surface_y": 88, "moisture": 0.45, "temperature": 0.60}
{"seed": 123456789, "x": 512, "z": 0, "surface_y": 64, "moisture": 0.80, "temperature": 0.48}
{"seed": 123456789, "x": 0, "z": 256, "surface_y": 102, "moisture": 0.35, "temperature": 0.72}
{"seed": 123456789, "x": 256, "z": 256, "surface_y": 76, "moisture": 0.62, "temperature": 0.58}
{"seed": 987654321, "x": 0, "z": 0, "surface_y": 54, "moisture": 0.90, "temperature": 0.40}
{"seed": 987654321, "x": 256, "z": 0, "surface_y": 116, "moisture": 0.28, "temperature": 0.78}
{"seed": 987654321, "x": 0, "z": 256, "surface_y": 69, "moisture": 0.74, "temperature": 0.52}
+257
View File
@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""Train and export the small terrain MLP used by the Fabric mod.
Input is JSONL. Each line must contain:
{"seed": 1, "x": 0, "z": 0, "surface_y": 72,
"moisture": 0.5, "temperature": 0.6}
The exporter writes format=1, which is consumed by JsonTerrainModel.java.
This script intentionally uses only the Python standard library so a model can
be trained without installing NumPy, PyTorch, or ONNX Runtime.
"""
from __future__ import annotations
import argparse
import json
import math
import random
from pathlib import Path
def features(seed: int, x: int, z: int) -> list[float]:
folded = (seed ^ (seed >> 33) ^ (seed << 11)) & ((1 << 64) - 1)
if folded >= (1 << 63):
folded -= 1 << 64
offset = folded * 0.00000017
return [
math.sin(x * 0.0041),
math.cos(x * 0.0041),
math.sin(z * 0.0037),
math.cos(z * 0.0037),
math.sin((x + z) * 0.0023),
math.cos((x - z) * 0.0027),
math.sin(x * 0.013 + z * 0.009),
math.cos(x * 0.011 - z * 0.015),
math.sin((x + offset) * 0.00091),
math.cos((z - offset) * 0.00107),
]
def sigmoid(value: float) -> float:
value = max(-30.0, min(30.0, value))
return 1.0 / (1.0 + math.exp(-2.0 * value))
def cave_features(seed: int, x: int, y: int, z: int) -> list[float]:
folded = (seed ^ (seed >> 29) ^ (seed << 17)) & ((1 << 64) - 1)
if folded >= (1 << 63):
folded -= 1 << 64
offset = folded * 0.00000011
x_value = x + offset
z_value = z - offset
return [
math.sin(x_value * 0.035),
math.cos(x_value * 0.035),
math.sin(z_value * 0.037),
math.cos(z_value * 0.037),
math.sin(y * 0.12),
math.cos(y * 0.12),
math.sin((x_value + z_value + y) * 0.018),
math.cos((x_value - z_value + y) * 0.021),
]
class Model:
def __init__(self, hidden: int, rng: random.Random):
self.hidden = hidden
scale = math.sqrt(2.0 / (10 + hidden))
self.hw = [[rng.uniform(-scale, scale) for _ in range(10)] for _ in range(hidden)]
self.hb = [0.0 for _ in range(hidden)]
self.ow = [[rng.uniform(-scale, scale) for _ in range(hidden)] for _ in range(3)]
self.ob = [0.0, 0.0, 0.0]
def forward(self, inputs: list[float]) -> tuple[list[float], list[float]]:
hidden_values = []
for row, bias in zip(self.hw, self.hb):
hidden_values.append(math.tanh(sum(w * x for w, x in zip(row, inputs)) + bias))
raw = [sum(w * h for w, h in zip(row, hidden_values)) + bias for row, bias in zip(self.ow, self.ob)]
outputs = [math.tanh(raw[0]), sigmoid(raw[1]), sigmoid(raw[2])]
return hidden_values, outputs
def train_one(self, inputs: list[float], target: list[float], learning_rate: float) -> float:
hidden_values, outputs = self.forward(inputs)
loss = sum((out - goal) ** 2 for out, goal in zip(outputs, target)) / 3.0
output_delta = [
2.0 * (outputs[0] - target[0]) * (1.0 - outputs[0] ** 2),
2.0 * (outputs[1] - target[1]) * 2.0 * outputs[1] * (1.0 - outputs[1]),
2.0 * (outputs[2] - target[2]) * 2.0 * outputs[2] * (1.0 - outputs[2]),
]
hidden_delta = []
for index, value in enumerate(hidden_values):
downstream = sum(self.ow[output][index] * output_delta[output] for output in range(3))
hidden_delta.append(downstream * (1.0 - value * value))
for output in range(3):
for index in range(self.hidden):
self.ow[output][index] -= learning_rate * output_delta[output] * hidden_values[index]
self.ob[output] -= learning_rate * output_delta[output]
for index in range(self.hidden):
for feature in range(10):
self.hw[index][feature] -= learning_rate * hidden_delta[index] * inputs[feature]
self.hb[index] -= learning_rate * hidden_delta[index]
return loss
def export(self, base_height: float, height_amplitude: float, cave_model: "CaveModel | None" = None) -> dict:
payload = {
"format": 1,
"inputs": 10,
"hidden": self.hidden,
"hidden_weights": self.hw,
"hidden_bias": self.hb,
"height_weights": self.ow[0],
"moisture_weights": self.ow[1],
"temperature_weights": self.ow[2],
"height_bias": self.ob[0],
"moisture_bias": self.ob[1],
"temperature_bias": self.ob[2],
"base_height": base_height,
"height_amplitude": height_amplitude,
}
if cave_model is not None:
payload.update(cave_model.export())
return payload
class CaveModel:
def __init__(self, hidden: int, rng: random.Random):
self.hidden = hidden
scale = math.sqrt(2.0 / (8 + hidden))
self.hw = [[rng.uniform(-scale, scale) for _ in range(8)] for _ in range(hidden)]
self.hb = [0.0 for _ in range(hidden)]
self.ow = [rng.uniform(-scale, scale) for _ in range(hidden)]
self.ob = 0.0
def forward(self, inputs: list[float]) -> tuple[list[float], float]:
hidden_values = [
math.tanh(sum(w * x for w, x in zip(row, inputs)) + bias)
for row, bias in zip(self.hw, self.hb)
]
output = sigmoid(sum(w * h for w, h in zip(self.ow, hidden_values)) + self.ob)
return hidden_values, output
def train_one(self, inputs: list[float], target: float, learning_rate: float) -> float:
hidden_values, output = self.forward(inputs)
loss = (output - target) ** 2
output_delta = 2.0 * (output - target) * 2.0 * output * (1.0 - output)
hidden_delta = [
self.ow[index] * output_delta * (1.0 - value * value)
for index, value in enumerate(hidden_values)
]
for index in range(self.hidden):
self.ow[index] -= learning_rate * output_delta * hidden_values[index]
self.ob -= learning_rate * output_delta
for index in range(self.hidden):
for feature in range(8):
self.hw[index][feature] -= learning_rate * hidden_delta[index] * inputs[feature]
self.hb[index] -= learning_rate * hidden_delta[index]
return loss
def export(self) -> dict:
return {
"cave_hidden": self.hidden,
"cave_hidden_weights": self.hw,
"cave_hidden_bias": self.hb,
"cave_weights": self.ow,
"cave_bias": self.ob,
}
def load_samples(path: Path, base_height: float, height_amplitude: float) -> list[tuple[list[float], list[float]]]:
samples = []
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if not line.strip():
continue
row = json.loads(line)
required = ("seed", "x", "z", "surface_y", "moisture", "temperature")
missing = [key for key in required if key not in row]
if missing:
raise ValueError(f"line {line_number}: missing {', '.join(missing)}")
height = max(-1.0, min(1.0, (float(row["surface_y"]) - base_height) / height_amplitude))
samples.append((features(int(row["seed"]), int(row["x"]), int(row["z"])), [
height,
max(0.0, min(1.0, float(row["moisture"]))),
max(0.0, min(1.0, float(row["temperature"]))),
]))
if not samples:
raise ValueError("training file contains no samples")
return samples
def load_cave_samples(path: Path) -> list[tuple[list[float], float]]:
samples = []
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if not line.strip():
continue
row = json.loads(line)
required = ("seed", "x", "y", "z", "cave")
missing = [key for key in required if key not in row]
if missing:
raise ValueError(f"cave line {line_number}: missing {', '.join(missing)}")
samples.append((
cave_features(int(row["seed"]), int(row["x"]), int(row["y"]), int(row["z"])),
max(0.0, min(1.0, float(row["cave"]))),
))
if not samples:
raise ValueError("cave training file contains no samples")
return samples
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input", required=True, type=Path, help="JSONL training samples")
parser.add_argument("--output", required=True, type=Path, help="model JSON output")
parser.add_argument("--epochs", type=int, default=200)
parser.add_argument("--hidden", type=int, default=24)
parser.add_argument("--lr", type=float, default=0.01)
parser.add_argument("--seed", type=int, default=1234)
parser.add_argument("--base-height", type=float, default=68.0)
parser.add_argument("--height-amplitude", type=float, default=46.0)
parser.add_argument("--cave-input", type=Path, help="optional JSONL cave samples")
parser.add_argument("--cave-hidden", type=int, default=16)
parser.add_argument("--cave-epochs", type=int, default=200)
parser.add_argument("--cave-lr", type=float, default=0.01)
args = parser.parse_args()
if args.hidden < 1 or args.hidden > 256:
parser.error("--hidden must be between 1 and 256")
samples = load_samples(args.input, args.base_height, args.height_amplitude)
rng = random.Random(args.seed)
model = Model(args.hidden, rng)
for epoch in range(1, args.epochs + 1):
rng.shuffle(samples)
loss = sum(model.train_one(inputs, target, args.lr) for inputs, target in samples) / len(samples)
if epoch == 1 or epoch % 10 == 0 or epoch == args.epochs:
print(f"epoch={epoch:04d} loss={loss:.6f}")
cave_model = None
if args.cave_input is not None:
if args.cave_hidden < 1 or args.cave_hidden > 128:
parser.error("--cave-hidden must be between 1 and 128")
cave_samples = load_cave_samples(args.cave_input)
cave_model = CaveModel(args.cave_hidden, rng)
for epoch in range(1, args.cave_epochs + 1):
rng.shuffle(cave_samples)
loss = sum(cave_model.train_one(inputs, target, args.cave_lr) for inputs, target in cave_samples) / len(cave_samples)
if epoch == 1 or epoch % 10 == 0 or epoch == args.cave_epochs:
print(f"cave_epoch={epoch:04d} cave_loss={loss:.6f}")
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(model.export(args.base_height, args.height_amplitude, cave_model), indent=2), encoding="utf-8")
print(f"wrote {args.output} with {len(samples)} samples")
if __name__ == "__main__":
main()
+604
View File
@@ -0,0 +1,604 @@
{
"format": 1,
"inputs": 10,
"hidden": 24,
"hidden_weights": [
[
0.2185670441244166,
-0.033330147500526965,
-0.2422722319189743,
0.19331148782951166,
0.20646000650741103,
0.03400276734669141,
0.08340351678371281,
-0.18536644553936174,
0.12194896754748526,
-0.1257608968404284
],
[
-0.226201528570982,
0.1438838280184002,
-0.07318367082039196,
0.06517931299257711,
0.0584117240562779,
-0.16545656717726825,
-0.15324506942411073,
-0.180685428962578,
-0.23384009548353124,
-0.00998109260305793
],
[
0.23718206991565913,
-0.20347580005113677,
0.021195227357443194,
-0.005120229820054289,
0.056716860582958814,
-0.18950281993218046,
0.036013770832328276,
-0.13384920733015274,
0.028725402829149645,
0.06303107268762347
],
[
-0.0019499635353757401,
-0.06786526539701704,
-0.12202881378857643,
0.21179081708075778,
-0.01980306365369147,
0.016359115140468887,
-0.23582756096583854,
-0.02504202744287233,
-0.23724798886017223,
-0.17227942739291335
],
[
-0.01884765040791486,
-0.06271823352850243,
-0.21652781413963476,
0.037820077881037736,
-0.16626066069479528,
0.023853482996329355,
-0.17129077448355465,
0.22540257781519088,
0.13051870243119554,
0.22445080059954464
],
[
-0.172657349953437,
-0.09152470860445318,
-0.22229705989546206,
-0.10337693330016322,
0.1507302494135973,
-0.15230346524943703,
-0.1671058593893696,
0.22694206844969878,
-0.16600224222535276,
0.15882099385249746
],
[
-0.2177611465788231,
-0.05125215135159282,
-0.07088618352241557,
-0.07206060159073255,
0.15758800103042575,
-0.006982751017105781,
0.13702394420786346,
-0.02403019555246904,
0.15761363455076136,
0.18290319694563717
],
[
-0.04038776198454205,
0.13105592512763578,
0.1507201223178027,
-0.10689022056312399,
-0.18993907068342908,
-0.1591586308123139,
-0.02967181035760224,
-0.1568599831305028,
0.009697073483039765,
0.1632835377640748
],
[
0.0032939494487721067,
0.15858274888215793,
0.07599377156248754,
0.07646585631877025,
-0.06907235360113313,
0.10492655023452484,
0.14712799908775304,
-0.18673643265771905,
0.19796176269096058,
-0.11706640986085516
],
[
-0.1703727180294046,
0.16443284534631603,
0.10767914127208254,
0.14077753894048348,
-0.026818597699523754,
-0.20921588161785123,
-0.04958426232061737,
-0.21112623860216506,
-0.10255621246402116,
-0.2228754342797477
],
[
-0.004279881920222554,
-0.20428022897195292,
0.20880344756324937,
0.09061890710257532,
-0.09370106087874863,
0.21046256902498897,
-0.20899127026647793,
-0.10099584566977136,
-0.21058689308484238,
-0.032825934781893364
],
[
-0.15032103117391335,
-0.05772825688267302,
0.09520707909064267,
0.176256161482374,
-0.006833603118516318,
0.14390633728722074,
-0.06843882525384375,
0.16878220441073546,
0.001451658937461217,
0.10512240031410404
],
[
-0.15326336833869464,
0.04583180477651807,
-0.09306057179083436,
0.15811867335347654,
0.021609779890134028,
0.005995744536228643,
0.13317547547443906,
0.01991593412381272,
-0.07573935220657425,
-0.18224749386020567
],
[
0.06719707951052831,
0.20829595946461313,
0.16671940831935905,
-0.20671278578076174,
-0.08106849864834584,
-0.2397608693440087,
0.0831453988584987,
0.043131466775809,
0.12745669561896072,
-0.170826859005501
],
[
-0.1302694147521545,
-0.028192265967203093,
0.11735312688570321,
-0.1374948760855445,
0.161830927887073,
-0.05278075419462908,
0.18087518781739073,
0.203553800431464,
0.02452103108650518,
0.20323057335650752
],
[
-0.044667924733216534,
0.08541134184162474,
0.16672475841087137,
0.15975746448400321,
-0.20191887025563235,
-0.12087923892532537,
-0.042517150789004,
0.19420155921177243,
-0.10939297641752296,
-0.1848135374270941
],
[
-0.05632705065427255,
-0.13784476173671303,
0.10463229205323071,
0.043012589377108966,
-0.21545417253797933,
0.18365402385683952,
-0.030208772346871547,
0.11728742099037806,
-0.16291270847610206,
-0.09331889411830227
],
[
-0.20185962006086008,
-0.0454052873559827,
-0.03177905117338425,
0.09675506605727956,
-0.004669824853397875,
-0.0408558111927726,
-0.01033933891553676,
-0.1079232227372705,
-0.05923315969056298,
0.0440684855226581
],
[
-0.14425818440699334,
0.0076089829453076544,
0.20709146927252275,
-0.022334492228379105,
0.07978603472560468,
0.07572711446435244,
0.14585288216388884,
0.11271913180527326,
0.07606363863956299,
-0.19151648887647568
],
[
-0.06663674643187555,
0.1400185061194408,
-0.21350879442837167,
-0.17740005199402897,
0.19366418544922534,
0.03957103903364213,
0.23195128571449286,
0.12495031925864637,
0.23665458743480025,
-0.09657896916042522
],
[
0.08701418422933549,
-0.08370464943967858,
0.215103453467403,
-0.10137526941629643,
-0.15056177629688722,
0.1509045539194283,
-0.10697032986729756,
0.02732208933495833,
0.047489494756915364,
-0.07754978956528288
],
[
-0.07790891813748817,
-0.0817833717413221,
-0.10974464664914707,
0.13421062996648908,
0.04775101745152519,
0.03082576557584207,
0.15855566512212713,
-0.10680366933301881,
-0.17077993262187974,
-0.2334438917521444
],
[
-0.21499397097763437,
0.24247742487648985,
0.23032038808073185,
0.086931005441903,
0.24328933546200202,
0.012834912017173784,
-0.03383422965041587,
-0.018518528781462724,
-0.20949611054027462,
0.12187909440592334
],
[
-0.0858936062136932,
0.10140072071592254,
0.200854418148771,
-0.12858482989243564,
0.008467997498585688,
0.08107055566165743,
0.14978160868311996,
0.07084263403295643,
-0.07836809485040248,
0.027153417072053578
]
],
"hidden_bias": [
-0.007768529649672775,
0.006134518739833867,
0.012069474273134791,
0.0013256800724403433,
-0.0047763837927318024,
0.005429205153036813,
0.005784440345676926,
-0.008780222786806655,
0.00811794180257231,
-0.0011591757461627,
-0.006686401160890214,
-0.013772165525523319,
0.00794795131943743,
0.0023278197371424687,
0.005867194621140073,
-0.005363812694959648,
0.00043207342537035574,
0.009471898551107548,
-0.0023607574863043317,
-0.004113574606567068,
-0.001971733568513393,
0.009805244533638997,
0.006367586846891039,
0.008565517405302274
],
"height_weights": [
-0.1064721646998083,
0.004814447498052047,
0.22303492789527193,
0.22630754310163823,
-0.21333724696617426,
-0.03874834102256639,
0.04298742271946956,
-0.17381540236544343,
0.25711341834563683,
-0.09952193927448671,
-0.14505554541483273,
-0.012551422644809586,
0.003714623508450303,
0.11881891185997147,
0.12911865894646232,
-0.10400776660141152,
0.07855627218698046,
0.026946635645685474,
0.010916451613703578,
0.039770522120398666,
-0.022794045710823947,
-0.04641126025863832,
0.21597794592309555,
0.10992351013742185
],
"moisture_weights": [
0.08590511034368754,
0.17801149374100922,
0.04033775821608458,
-0.20377656232106586,
-0.12674596725758216,
0.14550532796192506,
-0.07951962904483739,
0.1991351302226478,
0.09304804237110995,
-0.06436381844130347,
-0.005467658602163148,
-0.2052052552653854,
-0.018581206773029393,
-0.2040695405062645,
-0.09748982116694788,
0.2191610640688141,
-0.1634039739695805,
0.07987872457793564,
0.08357785954817325,
0.10104662522063328,
0.1445696112675592,
0.17669092564039257,
-0.06594642888640057,
-0.05894138210755217
],
"temperature_weights": [
-0.13838599494831968,
0.060289313818921636,
-0.14293337692747746,
-0.14704060683978704,
0.2268531927777024,
0.036230033657568064,
0.10681264887953533,
-0.007709000103668415,
-0.16096001562540113,
0.2024177565680835,
0.07564127933984985,
-0.21545019008953353,
0.20438868004027597,
-0.01770506109327772,
0.03825491219714065,
-0.06352390793405154,
-0.05520178836930493,
0.12953521789720296,
-0.12097896722691873,
-0.23305743626898476,
-0.08783239295367104,
0.23827078812054978,
-0.17746758395662518,
0.047236196925788496
],
"height_bias": 0.07062142913292503,
"moisture_bias": 0.024979703498789505,
"temperature_bias": 0.042786986721584395,
"base_height": 68.0,
"height_amplitude": 46.0,
"cave_hidden": 16,
"cave_hidden_weights": [
[
-0.12847771012203166,
0.21226464176181448,
-0.18746753347961134,
0.25915001915380204,
0.05052681833811209,
0.20007883950941618,
0.01751921075282904,
0.1457394194193605
],
[
0.04822986782598677,
0.18939132903669917,
0.007712867073556461,
0.12796006079318747,
0.17463772706799852,
-0.07298698511169403,
-0.2295639741705894,
0.10225511616108804
],
[
0.2042438984397157,
0.1928770474147084,
-0.2147697504408327,
0.16561390196369952,
-0.006306546633527892,
0.02640861028968013,
-0.212400914417072,
0.15451280433183456
],
[
-0.1489845784789246,
0.11468029169300517,
-0.09465013241501462,
0.26479872643758884,
0.12259213814338533,
-0.08394402039933242,
0.02781669519334323,
-0.18086989494792227
],
[
-0.01271502823736431,
0.24875442282493326,
0.2484885399295765,
-0.10201516881028769,
-0.03305961700039915,
0.013898124062983283,
0.02275483818745937,
0.2808517478954531
],
[
-0.24154147877793794,
-0.17464669461374938,
0.06156772329275407,
-0.09650850838371808,
-0.07124207405731581,
0.11279833108389312,
-0.17348383612121251,
-0.2702898529638631
],
[
0.26962444336169766,
-0.10620104707595467,
-0.23164971327742562,
-0.25809724934007733,
0.1030425002426775,
-0.04361431908088612,
-0.09248520409102917,
0.27821528323797134
],
[
0.24570065758278903,
0.12409107062687572,
0.23179631833475042,
-0.19440733289458734,
0.27217941599031314,
-0.2832788449333981,
0.05574902768162631,
0.2447732834689834
],
[
0.23621493847528874,
0.11768540326789764,
0.24584994867530413,
0.0628409048992164,
-0.19865946556036912,
0.24298101806724012,
-0.17650764266707722,
-0.026911714991771554
],
[
-0.029203097851036773,
0.0010759140521131511,
-0.09379655875740815,
-0.13171033258096163,
0.22528778681546488,
-0.06281567265815519,
0.10720714340985991,
-0.005198803513371119
],
[
-0.14494344700478357,
-0.12342279051569914,
0.27166010238948074,
0.23370087499644365,
-0.19740713103520804,
-0.1545102153977299,
0.2650079482306277,
0.24293714331960004
],
[
-0.0824182116649941,
0.025324246354382896,
-0.039863306237094304,
-0.030786379966705274,
0.029343022251614875,
0.16137661450113788,
0.1260506497369901,
0.12442702619110917
],
[
-0.24822430511493918,
-0.14760025374854543,
0.0844513857569687,
0.12310009280958421,
-0.008529213968216098,
0.1614232321523973,
0.26363821540126925,
0.11136596274785998
],
[
0.19435904337832202,
-0.09959833983623462,
0.18393142932277512,
0.012265311806330281,
0.17708476041217014,
0.10482918785808923,
-0.11373336549416867,
0.15459321141644658
],
[
0.17508743186393902,
0.0006693554932434597,
-0.20569185440513052,
-0.07047430396511359,
-0.18577898960094372,
-0.2098845797742763,
-0.11460195014857232,
-0.007811856137514603
],
[
0.1723921878993477,
0.058297229792411906,
-0.13490873074526386,
0.23965303454784695,
0.2589947266532829,
0.2080168201124156,
-0.01627605045280859,
0.21554219728305388
]
],
"cave_hidden_bias": [
0.0018253206150909362,
-0.001627776281104849,
0.0015483198758240802,
0.0012819636532847268,
0.0023728323472704672,
-0.0019127188097154382,
-0.001713064156522274,
-0.0018757683517227981,
-0.0003372256024722314,
0.0019603616692598857,
0.0005543110257504682,
0.001511906526424683,
-0.0003174102101931074,
0.0018214507940379448,
0.0015673845798080187,
-0.001133229041404879
],
"cave_weights": [
-0.1906261008850756,
0.16662963567977476,
-0.15754266901982286,
-0.14630693296687808,
-0.24742310063921327,
0.19001227356315337,
0.1833404185097436,
0.18885922721967607,
0.032165145249356,
-0.2039275253628742,
-0.06021514864395927,
-0.16028469643968785,
0.03763081959016224,
-0.20634727843716702,
-0.16657402935474425,
0.12662426384573505
],
"cave_bias": -0.009714086517113443
}
+604
View File
@@ -0,0 +1,604 @@
{
"format": 1,
"inputs": 10,
"hidden": 24,
"hidden_weights": [
[
0.22043124394207672,
-0.032763737796622984,
-0.24167325031252984,
0.1941986405445254,
0.2078733406427158,
0.03485476077625569,
0.08315979105773677,
-0.19003381000809885,
0.12378355452884651,
-0.12572546433038237
],
[
-0.2267148804170286,
0.14269293921079454,
-0.07365034606402782,
0.06345326182813019,
0.057659630455386364,
-0.16705319439514854,
-0.1533750931782991,
-0.18255206431230706,
-0.23439745108707577,
-0.008857222926718833
],
[
0.23449143556503552,
-0.20450966463824763,
0.021458384631414508,
-0.006910255484443933,
0.05545751175154049,
-0.1909129603786145,
0.036955836736488334,
-0.12734564913106844,
0.028915004898976214,
0.06408931838928139
],
[
-0.003347735207350866,
-0.06725248673052178,
-0.12129315489394311,
0.21308045332047504,
-0.019658794801138385,
0.017475882645481375,
-0.2347804366979651,
-0.01599788605962288,
-0.2374527845284059,
-0.17361782915029353
],
[
-0.017684158950294238,
-0.06272569208297284,
-0.21682132275516391,
0.03789681183002834,
-0.1659191411449662,
0.023812699652192765,
-0.1717749590976571,
0.22086121408092627,
0.13041244353903153,
0.22446242148226844
],
[
-0.1728781697003382,
-0.09247146077853223,
-0.22259473500164287,
-0.10493146984457741,
0.15021196375386492,
-0.15364783031028,
-0.16730203219213094,
0.22424454662909563,
-0.16626049546128577,
0.15989296048492135
],
[
-0.21893521437966623,
-0.05205357039313969,
-0.07135163765567243,
-0.07295874249745644,
0.15666262489146893,
-0.00792112363620823,
0.1371476808356312,
-0.021230097789226427,
0.1567203369324505,
0.18323290581786633
],
[
-0.0375709523126179,
0.13161438782059595,
0.15085658730110077,
-0.10579739451371946,
-0.1883497511358895,
-0.15835699668244807,
-0.030268204952091666,
-0.16531987451056104,
0.010865149375423327,
0.16312354245943342
],
[
0.0007568941475991466,
0.15850476907237485,
0.07647120178943233,
0.07565330085764065,
-0.07006100714721976,
0.1044530307737554,
0.1483192679761033,
-0.18005730783384627,
0.19815490016789095,
-0.11666828031488605
],
[
-0.17012280863771703,
0.16389609783682282,
0.10702710694248038,
0.1402831040617432,
-0.027197231171667613,
-0.20984051287239597,
-0.05004664025321824,
-0.21303647531212794,
-0.103513821645815,
-0.22269381462530244
],
[
-0.002715097533690285,
-0.20403725523132424,
0.20866031486152786,
0.09113351786373872,
-0.09301815798493274,
0.21084378923641026,
-0.20959487573136473,
-0.10567016682263107,
-0.21011471672859677,
-0.03287649023011823
],
[
-0.14842041032853362,
-0.055351279427141444,
0.09654781600568291,
0.1799404956422956,
-0.0045273195091172155,
0.1472636308474478,
-0.06811300976288034,
0.1702469870390216,
0.0034412354111806234,
0.10297204380540355
],
[
-0.15476722209664648,
0.0442975882207212,
-0.09407040015727386,
0.15609469360655026,
0.019987107667570836,
0.004016795365851872,
0.1330060601396559,
0.02130031173145944,
-0.0773831511323868,
-0.18125090743905575
],
[
0.06571422141348117,
0.2086213383914969,
0.1669374371241117,
-0.20611920208884377,
-0.08149921000706413,
-0.23923074642271067,
0.08379501555166911,
0.05034232422647743,
0.12715809472563785,
-0.17154751765463105
],
[
-0.1317267942602007,
-0.0285173589552202,
0.11710032214463797,
-0.13762824987707428,
0.1610342053647485,
-0.05305251470332906,
0.18129571144408071,
0.20910807767161788,
0.023196954040533645,
0.20276475015592554
],
[
-0.04285836471262029,
0.08619138534632931,
0.16694022157592298,
0.16038788872082094,
-0.2009591395633036,
-0.12018010662716556,
-0.04296054734512892,
0.1875877786245476,
-0.10923706919866284,
-0.18505404147834936
],
[
-0.05715264999363345,
-0.1375161592307064,
0.10501700544367262,
0.04364974523828535,
-0.21544399201423955,
0.1842403060485172,
-0.02968638012059459,
0.12234895297001526,
-0.16239220459047568,
-0.09378896105725075
],
[
-0.20333805794667667,
-0.04690570023607575,
-0.032547494235990834,
0.09450392398713219,
-0.006171611828031536,
-0.04291389126653235,
-0.010403906450229506,
-0.1074102864450325,
-0.06046824462883851,
0.045332529673027276
],
[
-0.14361889345222129,
0.00814732824146295,
0.2075469542322256,
-0.021718853958599016,
0.08046039687887967,
0.07637344258510928,
0.1459468720077955,
0.11146658759854786,
0.07673877475836327,
-0.19177890074273837
],
[
-0.06570957934315365,
0.1411147385530514,
-0.21254037056054678,
-0.17614817276720143,
0.19489260442930048,
0.04090575160178523,
0.23226462737074705,
0.12392529606871111,
0.23808970492455225,
-0.09713069222929627
],
[
0.08790824179065718,
-0.08344034373477083,
0.21541174821877832,
-0.10123854011641423,
-0.14994650925516748,
0.1511303721909609,
-0.1070731030454225,
0.023895793196856183,
0.04810089545466528,
-0.07742201884084363
],
[
-0.079106898693411,
-0.08376706911100798,
-0.11092490452474306,
0.13105609555699513,
0.04593191196635366,
0.02796635583089014,
0.15810407319567357,
-0.1098533350782804,
-0.17285112794942822,
-0.23168342132896141
],
[
-0.21709617534714712,
0.24229940975848258,
0.23068295249743517,
0.08665368398299347,
0.2425596427207101,
0.01271540552601482,
-0.03304166350486831,
-0.010478561857460494,
-0.20873455727597875,
0.1221112768173178
],
[
-0.08802033008184373,
0.10056392974586131,
0.2005247403392833,
-0.12984452027259813,
0.007109683983200273,
0.0799427986287582,
0.15024121504383744,
0.07612984609502828,
-0.07910884880292787,
0.027710421260015657
]
],
"hidden_bias": [
-0.006574396992814765,
0.0041692950572158865,
0.010414317532582599,
0.002992343790567531,
-0.004850073924808969,
0.00372210918809836,
0.0046477527436605045,
-0.007617458007755186,
0.007550070822110092,
-0.001987780252865075,
-0.006245146355865938,
-0.009400699514173829,
0.0054064313845204395,
0.00303314387164369,
0.005604257611182285,
-0.004622962913909033,
0.0012663960992733857,
0.006826927014192693,
-0.0015116817537772704,
-0.002365375126451454,
-0.0016770022468164574,
0.0060458242810515535,
0.0062760807825727365,
0.007136866119337299
],
"height_weights": [
-0.12017059903981916,
0.0020811198894908813,
0.2176121056361305,
0.22452225628571065,
-0.20353401452717454,
-0.02643470262328474,
0.04555865982240978,
-0.1751364763665603,
0.2481331084376102,
-0.10588423343971365,
-0.1493762608168526,
-0.006308141878849253,
0.004855185127248527,
0.12205767452719808,
0.13961798841082015,
-0.0964976106610137,
0.08514977602661665,
0.026488488136142116,
0.015146252206033188,
0.043568808675863394,
-0.023061588027596384,
-0.04985079992607864,
0.21508480965715554,
0.1151126088590581
],
"moisture_weights": [
0.08577182912146675,
0.18095973855440173,
0.04360650546261791,
-0.2051212562889134,
-0.12843226006893466,
0.1458884181199781,
-0.07794176938288706,
0.20368931214565097,
0.0925946335694216,
-0.06242466451708798,
-0.004828485535434452,
-0.20890909384674566,
-0.02078744060258256,
-0.20386774551626338,
-0.09841772653758801,
0.21557900456636547,
-0.16575618468481387,
0.08171015929024356,
0.08020417130864017,
0.09869432294565342,
0.1439880337407961,
0.17653994191778613,
-0.06708147078272625,
-0.060087107989287555
],
"temperature_weights": [
-0.144525016164432,
0.0625926512767514,
-0.14228294502859692,
-0.14706351540950113,
0.23040085205911331,
0.043232593690468765,
0.10819203956167611,
-0.004856348686191772,
-0.16662456201071865,
0.20141688015516848,
0.07511109440552777,
-0.21613512900060614,
0.20325235484327187,
-0.017155392632285247,
0.040979193547049576,
-0.06277464846028795,
-0.05351965318208913,
0.13111769135126916,
-0.12337617067774949,
-0.23462964931384725,
-0.08872718672837021,
0.23736521680576936,
-0.17967500191796043,
0.04747719260023359
],
"height_bias": 0.059005009484522045,
"moisture_bias": 0.016907906036375785,
"temperature_bias": 0.0294140137362916,
"base_height": 68.0,
"height_amplitude": 46.0,
"cave_hidden": 16,
"cave_hidden_weights": [
[
0.21465003304664332,
0.27052921599608215,
0.024169659341869175,
-0.05524271781671739,
-0.17666250198200598,
-0.12952096702470298,
0.21134635784449493,
-0.18591127924040937
],
[
0.2541515556486897,
0.050809272658215165,
0.1991972510234783,
0.014584409073764213,
0.14890890708068322,
0.051974262870865366,
0.18818651749680626,
0.01077466076753871
],
[
0.12652155990475358,
0.1760790305865667,
-0.07438312066481594,
-0.2319252530610173,
0.1024564230206979,
0.20369949634882506,
0.19180465653554368,
-0.21306816273431547
],
[
0.16445974533170094,
-0.006951027466920818,
0.026846289266463413,
-0.21233544087003126,
0.1555380503394103,
-0.15080404224232344,
0.1146812515981035,
-0.0952783221132968
],
[
0.2606017049168871,
0.12307297379149089,
-0.08483850882895382,
0.025118899349000915,
-0.17857900455331885,
-0.014196959440350598,
0.24789997065029448,
0.250089945636192
],
[
-0.10160569849689416,
-0.03446189386425376,
0.015330836869183205,
0.024783449816197418,
0.28146291022821607,
-0.24042010599236283,
-0.1737118470794088,
0.06018277354327671
],
[
-0.09709299072859237,
-0.06987176849836729,
0.11167464909570751,
-0.17543856454381515,
-0.2704881251309897,
0.27298108893696077,
-0.10706205275063188,
-0.2292672733865369
],
[
-0.2551847718504393,
0.10341977104096917,
-0.043420384038494436,
-0.09132169132493874,
0.27641524636054865,
0.24735707347930383,
0.12449096527486739,
0.23145087670350134
],
[
-0.19138338282738393,
0.27241750301832707,
-0.283136239894874,
0.056857171596075756,
0.24280482016408222,
0.2360394150960548,
0.11812484149698971,
0.24502406347742298
],
[
0.06660504733091945,
-0.1995828029614443,
0.2441570079164796,
-0.17355144360983685,
-0.02886188952132386,
-0.03339819076987812,
0.0022820454438568375,
-0.09709582208663739
],
[
-0.13589553823376882,
0.22526972422718627,
-0.06333652056382374,
0.1050791406218418,
-0.0025446164390323877,
-0.14452638852640146,
-0.12426695304255693,
0.2733125968441018
],
[
0.23110838042974644,
-0.19695253025933998,
-0.15517594623501568,
0.2631441771161285,
0.24451143816241702,
-0.08337511616426883,
0.02456467963230233,
-0.0386928188443107
],
[
-0.03480211256273539,
0.029556788579988977,
0.16064342937961562,
0.12375980121441685,
0.12686758959952682,
-0.24648676524639226,
-0.14853268260292404,
0.08657220313577671
],
[
0.12287166715035831,
-0.008224889307691392,
0.16118115173403344,
0.2631942797751518,
0.11135413852298788,
0.19216315360523342,
-0.09989791111670225,
0.18370881412024986
],
[
0.013473731845415332,
0.1756259148217431,
0.10629928858919815,
-0.11145902194927776,
0.15453616820220883,
0.17156839939563517,
0.001665904837567072,
-0.20845096626087614
],
[
-0.0710785085810423,
-0.18668515520345894,
-0.20930160390262958,
-0.11397155525709396,
-0.007052043630869222,
0.17348392187283862,
0.05867091344285837,
-0.13514603528118635
]
],
"cave_hidden_bias": [
-0.0027791820625650775,
-0.003177164239760205,
-0.0028126222885461708,
0.00024878981391606423,
-0.002964167909932488,
0.002471590126222696,
-0.0022692632677330793,
0.0012210850347597496,
0.0012154197552618439,
0.00332832742134316,
-0.002318782381213963,
-0.0020739886152062906,
-0.0025200666851644547,
-0.0005157782750179861,
0.002761932469713414,
0.0007969896349548089
],
"cave_weights": [
0.2342215018098955,
0.2552179965199922,
0.20845029914984786,
-0.017670037286058517,
0.2116596700954954,
-0.18910558150504714,
0.16905178221259393,
-0.1465087450424375,
-0.1359437218030301,
-0.2522520363923223,
0.19219285121159196,
0.17670930140329202,
0.18977156309613863,
0.034277630651018735,
-0.2034836544429928,
-0.059015598921802616
],
"cave_bias": -0.013767589094241684
}
+404
View File
@@ -0,0 +1,404 @@
{
"format": 1,
"inputs": 10,
"hidden": 24,
"hidden_weights": [
[
0.22043124394207672,
-0.032763737796622984,
-0.24167325031252984,
0.1941986405445254,
0.2078733406427158,
0.03485476077625569,
0.08315979105773677,
-0.19003381000809885,
0.12378355452884651,
-0.12572546433038237
],
[
-0.2267148804170286,
0.14269293921079454,
-0.07365034606402782,
0.06345326182813019,
0.057659630455386364,
-0.16705319439514854,
-0.1533750931782991,
-0.18255206431230706,
-0.23439745108707577,
-0.008857222926718833
],
[
0.23449143556503552,
-0.20450966463824763,
0.021458384631414508,
-0.006910255484443933,
0.05545751175154049,
-0.1909129603786145,
0.036955836736488334,
-0.12734564913106844,
0.028915004898976214,
0.06408931838928139
],
[
-0.003347735207350866,
-0.06725248673052178,
-0.12129315489394311,
0.21308045332047504,
-0.019658794801138385,
0.017475882645481375,
-0.2347804366979651,
-0.01599788605962288,
-0.2374527845284059,
-0.17361782915029353
],
[
-0.017684158950294238,
-0.06272569208297284,
-0.21682132275516391,
0.03789681183002834,
-0.1659191411449662,
0.023812699652192765,
-0.1717749590976571,
0.22086121408092627,
0.13041244353903153,
0.22446242148226844
],
[
-0.1728781697003382,
-0.09247146077853223,
-0.22259473500164287,
-0.10493146984457741,
0.15021196375386492,
-0.15364783031028,
-0.16730203219213094,
0.22424454662909563,
-0.16626049546128577,
0.15989296048492135
],
[
-0.21893521437966623,
-0.05205357039313969,
-0.07135163765567243,
-0.07295874249745644,
0.15666262489146893,
-0.00792112363620823,
0.1371476808356312,
-0.021230097789226427,
0.1567203369324505,
0.18323290581786633
],
[
-0.0375709523126179,
0.13161438782059595,
0.15085658730110077,
-0.10579739451371946,
-0.1883497511358895,
-0.15835699668244807,
-0.030268204952091666,
-0.16531987451056104,
0.010865149375423327,
0.16312354245943342
],
[
0.0007568941475991466,
0.15850476907237485,
0.07647120178943233,
0.07565330085764065,
-0.07006100714721976,
0.1044530307737554,
0.1483192679761033,
-0.18005730783384627,
0.19815490016789095,
-0.11666828031488605
],
[
-0.17012280863771703,
0.16389609783682282,
0.10702710694248038,
0.1402831040617432,
-0.027197231171667613,
-0.20984051287239597,
-0.05004664025321824,
-0.21303647531212794,
-0.103513821645815,
-0.22269381462530244
],
[
-0.002715097533690285,
-0.20403725523132424,
0.20866031486152786,
0.09113351786373872,
-0.09301815798493274,
0.21084378923641026,
-0.20959487573136473,
-0.10567016682263107,
-0.21011471672859677,
-0.03287649023011823
],
[
-0.14842041032853362,
-0.055351279427141444,
0.09654781600568291,
0.1799404956422956,
-0.0045273195091172155,
0.1472636308474478,
-0.06811300976288034,
0.1702469870390216,
0.0034412354111806234,
0.10297204380540355
],
[
-0.15476722209664648,
0.0442975882207212,
-0.09407040015727386,
0.15609469360655026,
0.019987107667570836,
0.004016795365851872,
0.1330060601396559,
0.02130031173145944,
-0.0773831511323868,
-0.18125090743905575
],
[
0.06571422141348117,
0.2086213383914969,
0.1669374371241117,
-0.20611920208884377,
-0.08149921000706413,
-0.23923074642271067,
0.08379501555166911,
0.05034232422647743,
0.12715809472563785,
-0.17154751765463105
],
[
-0.1317267942602007,
-0.0285173589552202,
0.11710032214463797,
-0.13762824987707428,
0.1610342053647485,
-0.05305251470332906,
0.18129571144408071,
0.20910807767161788,
0.023196954040533645,
0.20276475015592554
],
[
-0.04285836471262029,
0.08619138534632931,
0.16694022157592298,
0.16038788872082094,
-0.2009591395633036,
-0.12018010662716556,
-0.04296054734512892,
0.1875877786245476,
-0.10923706919866284,
-0.18505404147834936
],
[
-0.05715264999363345,
-0.1375161592307064,
0.10501700544367262,
0.04364974523828535,
-0.21544399201423955,
0.1842403060485172,
-0.02968638012059459,
0.12234895297001526,
-0.16239220459047568,
-0.09378896105725075
],
[
-0.20333805794667667,
-0.04690570023607575,
-0.032547494235990834,
0.09450392398713219,
-0.006171611828031536,
-0.04291389126653235,
-0.010403906450229506,
-0.1074102864450325,
-0.06046824462883851,
0.045332529673027276
],
[
-0.14361889345222129,
0.00814732824146295,
0.2075469542322256,
-0.021718853958599016,
0.08046039687887967,
0.07637344258510928,
0.1459468720077955,
0.11146658759854786,
0.07673877475836327,
-0.19177890074273837
],
[
-0.06570957934315365,
0.1411147385530514,
-0.21254037056054678,
-0.17614817276720143,
0.19489260442930048,
0.04090575160178523,
0.23226462737074705,
0.12392529606871111,
0.23808970492455225,
-0.09713069222929627
],
[
0.08790824179065718,
-0.08344034373477083,
0.21541174821877832,
-0.10123854011641423,
-0.14994650925516748,
0.1511303721909609,
-0.1070731030454225,
0.023895793196856183,
0.04810089545466528,
-0.07742201884084363
],
[
-0.079106898693411,
-0.08376706911100798,
-0.11092490452474306,
0.13105609555699513,
0.04593191196635366,
0.02796635583089014,
0.15810407319567357,
-0.1098533350782804,
-0.17285112794942822,
-0.23168342132896141
],
[
-0.21709617534714712,
0.24229940975848258,
0.23068295249743517,
0.08665368398299347,
0.2425596427207101,
0.01271540552601482,
-0.03304166350486831,
-0.010478561857460494,
-0.20873455727597875,
0.1221112768173178
],
[
-0.08802033008184373,
0.10056392974586131,
0.2005247403392833,
-0.12984452027259813,
0.007109683983200273,
0.0799427986287582,
0.15024121504383744,
0.07612984609502828,
-0.07910884880292787,
0.027710421260015657
]
],
"hidden_bias": [
-0.006574396992814765,
0.0041692950572158865,
0.010414317532582599,
0.002992343790567531,
-0.004850073924808969,
0.00372210918809836,
0.0046477527436605045,
-0.007617458007755186,
0.007550070822110092,
-0.001987780252865075,
-0.006245146355865938,
-0.009400699514173829,
0.0054064313845204395,
0.00303314387164369,
0.005604257611182285,
-0.004622962913909033,
0.0012663960992733857,
0.006826927014192693,
-0.0015116817537772704,
-0.002365375126451454,
-0.0016770022468164574,
0.0060458242810515535,
0.0062760807825727365,
0.007136866119337299
],
"height_weights": [
-0.12017059903981916,
0.0020811198894908813,
0.2176121056361305,
0.22452225628571065,
-0.20353401452717454,
-0.02643470262328474,
0.04555865982240978,
-0.1751364763665603,
0.2481331084376102,
-0.10588423343971365,
-0.1493762608168526,
-0.006308141878849253,
0.004855185127248527,
0.12205767452719808,
0.13961798841082015,
-0.0964976106610137,
0.08514977602661665,
0.026488488136142116,
0.015146252206033188,
0.043568808675863394,
-0.023061588027596384,
-0.04985079992607864,
0.21508480965715554,
0.1151126088590581
],
"moisture_weights": [
0.08577182912146675,
0.18095973855440173,
0.04360650546261791,
-0.2051212562889134,
-0.12843226006893466,
0.1458884181199781,
-0.07794176938288706,
0.20368931214565097,
0.0925946335694216,
-0.06242466451708798,
-0.004828485535434452,
-0.20890909384674566,
-0.02078744060258256,
-0.20386774551626338,
-0.09841772653758801,
0.21557900456636547,
-0.16575618468481387,
0.08171015929024356,
0.08020417130864017,
0.09869432294565342,
0.1439880337407961,
0.17653994191778613,
-0.06708147078272625,
-0.060087107989287555
],
"temperature_weights": [
-0.144525016164432,
0.0625926512767514,
-0.14228294502859692,
-0.14706351540950113,
0.23040085205911331,
0.043232593690468765,
0.10819203956167611,
-0.004856348686191772,
-0.16662456201071865,
0.20141688015516848,
0.07511109440552777,
-0.21613512900060614,
0.20325235484327187,
-0.017155392632285247,
0.040979193547049576,
-0.06277464846028795,
-0.05351965318208913,
0.13111769135126916,
-0.12337617067774949,
-0.23462964931384725,
-0.08872718672837021,
0.23736521680576936,
-0.17967500191796043,
0.04747719260023359
],
"height_bias": 0.059005009484522045,
"moisture_bias": 0.016907906036375785,
"temperature_bias": 0.0294140137362916,
"base_height": 68.0,
"height_amplitude": 46.0
}