122 lines
3.9 KiB
GDScript3
122 lines
3.9 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_planet_identity_persists.gd
|
||
|
|
# Regression test for: leaving a planet's surface and coming back
|
||
|
|
# respawned Main.tscn's PlanetField from scratch, and since planet
|
||
|
|
# name/position/shape were picked with the unseeded global RNG at spawn
|
||
|
|
# time, the "same" planet (same chunk) would come back with a different
|
||
|
|
# name, a different position within the chunk, and a different outline
|
||
|
|
# shape every single time. Fixed by seeding planet identity purely from
|
||
|
|
# the chunk's own coordinates (mirrors WorldGen's own deterministic noise
|
||
|
|
# pattern), so this loads Main.tscn twice and checks the planet at the
|
||
|
|
# same chunk is identical both times.
|
||
|
|
|
||
|
|
var target_chunk: Vector2i
|
||
|
|
var first_name: String
|
||
|
|
var first_pos: Vector2
|
||
|
|
var first_shape: PackedVector2Array
|
||
|
|
var stage: int = 0
|
||
|
|
var main_scene: Node
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
var found: bool = false
|
||
|
|
for x in range(-15, 15):
|
||
|
|
for y in range(-15, 15):
|
||
|
|
var chunk: Vector2i = Vector2i(x, y)
|
||
|
|
if WorldGen.has_planet_at_chunk(chunk):
|
||
|
|
target_chunk = chunk
|
||
|
|
found = true
|
||
|
|
break
|
||
|
|
if found:
|
||
|
|
break
|
||
|
|
|
||
|
|
if not found:
|
||
|
|
print("FAIL: could not find any chunk with a planet to test with")
|
||
|
|
quit(1)
|
||
|
|
return
|
||
|
|
|
||
|
|
_load_main()
|
||
|
|
|
||
|
|
|
||
|
|
func _load_main() -> void:
|
||
|
|
# GameState (an autoload) isn't accessible as a bare global identifier
|
||
|
|
# from a script that extends SceneTree directly - fetch it via root.
|
||
|
|
var game_state: Node = root.get_node("GameState")
|
||
|
|
game_state.has_return_position = true
|
||
|
|
game_state.surviving_ship_count = 5
|
||
|
|
game_state.return_world_position = WorldGen.chunk_origin(target_chunk) + Vector2.ONE * WorldGen.CHUNK_SIZE * 0.5
|
||
|
|
game_state.return_camera_zoom = 1.0
|
||
|
|
|
||
|
|
var packed: PackedScene = load("res://Main.tscn")
|
||
|
|
main_scene = packed.instantiate()
|
||
|
|
root.add_child(main_scene)
|
||
|
|
main_scene.get_node("AsteroidField").set_process(false)
|
||
|
|
main_scene.get_node("EnemySpawner").set_process(false)
|
||
|
|
|
||
|
|
|
||
|
|
func _process(_delta: float) -> bool:
|
||
|
|
match stage:
|
||
|
|
0:
|
||
|
|
# Wait one tick for _ready() (and the forced synchronous
|
||
|
|
# update_chunks() call inside it) to have actually run.
|
||
|
|
stage = 1
|
||
|
|
return false
|
||
|
|
1:
|
||
|
|
var planet_field: Node = main_scene.get_node("PlanetField")
|
||
|
|
if not planet_field.populated_chunks.has(target_chunk):
|
||
|
|
print("FAIL: planet never spawned in target chunk on first load")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
var planet: Node2D = planet_field.populated_chunks[target_chunk][0]
|
||
|
|
first_name = planet.planet_name
|
||
|
|
first_pos = planet.position
|
||
|
|
first_shape = planet.shape_points.duplicate()
|
||
|
|
|
||
|
|
main_scene.queue_free()
|
||
|
|
stage = 2
|
||
|
|
return false
|
||
|
|
2:
|
||
|
|
# Give queue_free() a moment to actually clear the old tree
|
||
|
|
# before loading a fresh Main.tscn on top of it.
|
||
|
|
elapsed += 1.0
|
||
|
|
if elapsed < 3.0:
|
||
|
|
return false
|
||
|
|
_load_main()
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 3
|
||
|
|
return false
|
||
|
|
3:
|
||
|
|
stage = 4
|
||
|
|
return false
|
||
|
|
4:
|
||
|
|
var planet_field: Node = main_scene.get_node("PlanetField")
|
||
|
|
if not planet_field.populated_chunks.has(target_chunk):
|
||
|
|
print("FAIL: planet never spawned in target chunk on second load")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
var planet: Node2D = planet_field.populated_chunks[target_chunk][0]
|
||
|
|
|
||
|
|
var name_ok: bool = planet.planet_name == first_name
|
||
|
|
var pos_ok: bool = planet.position.distance_to(first_pos) < 0.01
|
||
|
|
var shape_ok: bool = planet.shape_points.size() == first_shape.size()
|
||
|
|
if shape_ok:
|
||
|
|
for i in range(first_shape.size()):
|
||
|
|
if planet.shape_points[i].distance_to(first_shape[i]) > 0.01:
|
||
|
|
shape_ok = false
|
||
|
|
break
|
||
|
|
|
||
|
|
print("name: %s vs %s (ok=%s)" % [first_name, planet.planet_name, name_ok])
|
||
|
|
print("pos: %s vs %s (ok=%s)" % [first_pos, planet.position, pos_ok])
|
||
|
|
print("shape_ok=%s" % shape_ok)
|
||
|
|
|
||
|
|
if name_ok and pos_ok and shape_ok:
|
||
|
|
print("TEST_PLANET_IDENTITY_PERSISTS: PASS")
|
||
|
|
quit(0)
|
||
|
|
else:
|
||
|
|
print("TEST_PLANET_IDENTITY_PERSISTS: FAIL")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
return false
|