W0: terrain from worldlet building blocks (Valley in data/worldlets, identical heights), WorldInfo API, worldlet in saves and servers; the cliff tunnel plan

This commit is contained in:
Paul Bailey
2026-11-06 15:51:43 +00:00
parent f4df2bc99d
commit db4a659236
10 changed files with 329 additions and 19 deletions

View File

@@ -9,7 +9,7 @@ if [ ! -f .godot/global_script_class_cache.cfg ]; then
echo "Importing the project first (fresh copy)..."
godot --headless --path . --import >/dev/null 2>&1
fi
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_terrain_edges test_boundary; do
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_terrain_edges test_boundary; do
extra=""
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
# A suite that waits on a world that failed to load would hang forever,

89
tests/test_worldlets.gd Normal file
View File

@@ -0,0 +1,89 @@
extends SceneTree
## W0 (docs/WORLDLETS_DESIGN.md): the terrain is built from a worldlet's
## blocks (data/worldlets/<id>.json), and the Valley made that way is the same
## island, height for height, as the hand-coded one it replaced (the hashes
## below were taken from the old code). Also: the world answers the WorldInfo
## questions, a new save remembers its worldlet, an old save without one is
## the Valley, and a bad worldlet falls back to the Valley.
## Run: godot --headless --path . --script res://tests/test_worldlets.gd
## SHA-256 of the height grid, from the terrain before W0 (commit f4df2bc).
const GRID_HASHES := {
1337: "c87679a4ed30bff0c4be1caacda735454ad23f690ada28e7179667a9a7d62aa7",
777: "2edef348df09bf1f84ed3f5b0921f28fe54b3c827a698fac0a7d50fcdb95a24e",
42: "801430858909739fc8cf46694eee35c0cffa86ff1a9ad1ed379f1dfdea4b133c",
9001: "7d509d2508395a0e27e93a3384ac1e63323d588ef3cd1262f1e3f0c1c3e3254b",
}
var _fails := 0
var _finished := false
func check(cond: bool, what: String) -> void:
print((" ok " if cond else " FAIL ") + what)
if not cond:
_fails += 1
func _initialize() -> void:
_run.call_deferred()
func _run() -> void:
var S: Node = root.get_node("Saves")
S.root_dir = "user://test_worlds"
for w in S.list_worlds():
S.delete_world(w.id)
await _body(S)
check(_finished, "test ran to the end (no script errors)")
for w in S.list_worlds():
S.delete_world(w.id)
DirAccess.remove_absolute(S.root_dir)
print("RESULT: %s (%d failures)" % ["PASS" if _fails == 0 else "FAIL", _fails])
quit(1 if _fails > 0 else 0)
func _grid_hash(t: Node) -> String:
var h := HashingContext.new()
h.start(HashingContext.HASH_SHA256)
h.update(t._heights.to_byte_array())
return h.finish().hex_encode()
func _body(S: Node) -> void:
var T: Script = load("res://scripts/world/terrain.gd")
print("the Valley, from data")
var list: Array = T.worldlets()
check(list.any(func(w): return w.id == "valley"), "the Valley is a worldlet (%s)" % [list.map(func(w): return w.id)])
var def: Dictionary = T.load_worldlet("valley")
check(def.get("terrain", []).size() == 5, "built from 5 blocks: hills, rim, sea cliff, lake, ocean edge")
for s in GRID_HASHES:
var t: Node3D = T.new()
t.noise_seed = s
root.add_child(t)
await process_frame
check(_grid_hash(t) == GRID_HASHES[s], "seed %d: the same island, height for height" % s)
t.queue_free()
await process_frame
var bad: Node3D = T.new()
check(not bad.use_worldlet("nowhere").is_empty() and bad.worldlet_id == "valley", "an unknown worldlet is refused (the Valley stays)")
bad.free()
print("in a world")
var id: String = S.create_world("W0", 1337, {})
check(String(S.read_meta(id).get("worldlet", "")) == "valley", "a new world remembers its worldlet")
var meta: Dictionary = S.read_meta(id)
meta.erase("worldlet")
S._write_json(S._dir(id).path_join("meta.json"), meta)
S.current_id = id
var world: Node3D = load("res://scenes/world.tscn").instantiate()
root.add_child(world)
for _i in range(5):
await physics_frame
check(world.worldlet_id() == "valley", "an old world with no worldlet is the Valley")
var water: Array = world.water_bodies()
check(water.size() == 2 and water.any(func(w): return w.type == "lake") and water.any(func(w): return w.type == "sea"),
"its water: a lake and the sea")
var lake: Dictionary = water.filter(func(w): return w.type == "lake")[0]
var lp: Vector2 = lake.center
check(world.water_at(lp.x, lp.y).get("type", "") == "lake", "(and the lake is where it says)")
check(absf(float(world.climate().snowline) - 40.0) < 0.01, "its snowline: 40 m")
check(world.crossings().any(func(c): return c.to == "cove" and c.by == "tunnel"), "its way on: a tunnel to the Cove (planned)")
world.queue_free()
await process_frame
_finished = true