extends Node
## GATE — CURVES ARE ROOM STATE (#120 F5, CurveFold through LevelPersistence).
## J1 create_curve: the curve is in curve_states, points as given (auto handles), one
## gesture; the level is NOT re-folded and nothing is reported skipped
## J2 set_curve_point moves one point (one curve.set); Ctrl+Z puts it back
## J3 the whole point list, and closed, can be set
## J4 remove_curve: gone; Ctrl+Z: back
## J5 a point index past the end is skipped, never guessed at
## J6 two arrival orders of the same records give the same curves
## J7 the view draws it (a line strip + its points)
## J8 a flatten keeps it, the same points
##
## RUN AS A SCENE: CI=true "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_curve_journal.tscn
const CurveFold := preload("res://scenes/maps/curves/curve_fold.gd")
var fails := 0
var checks := 0
var map: Node3D
var persist: Node
func _ok(what: String, cond: bool, note: Variant = "") -> void:
checks += 1
if not cond:
fails += 1
print(" %-64s %s %s" % [what, "OK" if cond else "**FAIL**", str(note)])
func _ready() -> void:
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer")
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
get_tree().root.get_node("/root/GameMode").set_mode("build")
map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
await _run()
get_tree().root.get_node("/root/GameMode").set_mode("play")
if checks < 13:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE curve_journal: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _p(dot: String, i: int) -> Vector3:
return (map.curve_states()[dot]["points"][i] as Dictionary)["p"]
func _run() -> void:
persist = map.get("_persist")
persist.refold()
var pts: Array = [CurveMath.point(Vector3(0, 1, 0), Vector3.FORWARD), CurveMath.point(Vector3(4, 2, 0)),
CurveMath.point(Vector3(8, 1, 0), Vector3.BACK)]
# ---- J1 ----
var g0: int = (persist.gestures() as Array).size()
var c: String = map.create_curve(pts)
var lf: Dictionary = persist.last_fold()
var st: Dictionary = map.curve_states()
_ok("J1 the curve is room state", st.has(c), st.keys())
_ok("J1 ... its points where they were placed", st.has(c) and _p(c, 1).is_equal_approx(Vector3(4, 2, 0)))
_ok("J1 ... with auto handles (the first leaves along its normal)",
st.has(c) and ((st[c]["points"][0] as Dictionary)["h_out"] as Vector3).normalized().is_equal_approx(Vector3.FORWARD))
_ok("J1 one gesture", (persist.gestures() as Array).size() == g0 + 1)
_ok("J1 the level was not re-folded, nothing skipped", String(lf.get("brush_fold", "")) == "skipped"
and int(lf.get("skipped", 0)) == 0, lf)
# ---- J7 ----
_ok("J7 the view draws it: a strip and its points", map.curve_view_surfaces() == 2, map.curve_view_surfaces())
# ---- J2 ----
map.set_curve_point(c, 1, {"p": Vector3(4, 5, 0)})
_ok("J2 a point drag moves one point", _p(c, 1).is_equal_approx(Vector3(4, 5, 0)) and _p(c, 0).is_equal_approx(Vector3(0, 1, 0)))
persist.undo_last()
persist.refold()
_ok("J2 Ctrl+Z puts it back", _p(c, 1).is_equal_approx(Vector3(4, 2, 0)))
# ---- J3 ----
persist.begin_gesture("Edit")
persist.record("curve.set", {"target": c, "points": CurveFold.to_wire([CurveMath.point(Vector3.ZERO), CurveMath.point(Vector3(1, 0, 0)),
CurveMath.point(Vector3(1, 0, 1)), CurveMath.point(Vector3(0, 0, 1))]), "closed": true})
persist.end_gesture()
persist.refold()
_ok("J3 four points, closed", (map.curve_states()[c]["points"] as Array).size() == 4 and bool(map.curve_states()[c]["closed"]))
# ---- J5 ----
persist.begin_gesture("Edit")
persist.record("curve.set", {"target": c, "index": 9, "fields": {"p": [9, 9, 9]}})
persist.end_gesture()
persist.refold()
_ok("J5 an index past the end is skipped", (map.curve_states()[c]["points"] as Array).size() == 4
and not (map.curve_states()[c]["points"] as Array).any(func(q: Dictionary) -> bool: return (q["p"] as Vector3) == Vector3(9, 9, 9)))
persist.undo_last()
persist.undo_last()
persist.refold()
# ---- J4 ----
map.remove_curve(c)
_ok("J4 removed", not map.curve_states().has(c))
persist.undo_last()
persist.refold()
_ok("J4 Ctrl+Z: back", map.curve_states().has(c))
# ---- J6 ----
var recs: Array = (persist.records(false) as Array).filter(func(r: Dictionary) -> bool: return CurveFold.handles(String(r["kind"])))
var rev := recs.duplicate()
rev.reverse()
_ok("J6 another arrival order, the same curves", str(CurveFold.live(rev)) == str(CurveFold.live(recs)))
# ---- J8 ----
var before: Array = map.curve_states()[c]["points"]
persist.flatten_map()
var after: Array = []
for d: String in map.curve_states():
after = map.curve_states()[d]["points"]
_ok("J8 a flatten keeps the curve, the same points", map.curve_states().size() == 1 and after.size() == before.size()
and (after[1]["p"] as Vector3).is_equal_approx(before[1]["p"]), [before.size(), after.size()])