OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_editor_e5.gd

277 lines · gd ref:
extends SceneTree
## GATE — editor redesign E5 (docs/editor-redesign.md §8b), on a real map_flat.
##   E5a  GESTURES: a parametric stairs is ONE gesture of one shape.create with no Adjust
##        Last (§13 of parametric-shapes-design — its params live in the Shape panel); a
##        3-brush scale (three brush.scale) is ONE gesture — one Ctrl+Z puts all three
##        back, one redo scales them again;
##   E5b  ADJUST LAST: after the scale, changing Steps 1 -> 3 is one undo of that gesture +
##        one new gesture, and the brushes grew further; the panel shows; another edit ends it;
##   E5c  a two-brush rotate through the real keys is one gesture — one Ctrl+Z restores both;
##   E5d  a record written outside any gesture is a gesture of one (the legacy shape);
##   E5e  HISTORY lists gestures newest first with labels and counts, undone ones marked;
##        seek to an older row undoes the newer ones, seek to the newest redoes them.
##
## Run: HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_editor_e5.gd

const PINNED := ["tool.box.shape", "tool.box.size", "tool.box.height", "tool.box.p.stairs.steps",
	"tool.box.p.arch.segments", "tool.rotate.step", "snap", "grid"]

var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _level: Node
var _persist: Node
var _dock: Node
var _stack: Node
var _hist: Node
var _saved: Dictionary = {}
var _a := -1
var _b := -1


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	print(("ok   " if c else "FAIL: ") + m + ("" if str(got) == "" else "  (" + str(got) + ")"))
	if not c:
		ok = false


func _key(code: int) -> InputEventKey:
	var e := InputEventKey.new()
	e.physical_keycode = code
	e.keycode = code
	e.pressed = true
	return e


func _box(lo: Vector3, hi: Vector3) -> Array:
	return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]


func _make(lo: Vector3, hi: Vector3) -> String:
	var ft: Array = []
	for i in 6:
		ft.append({"tex": "kenney-prototype/dark_01", "mode": 1})
	var dot: String = _persist.record("brush.create", {"planes": _box(lo, hi), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "face_tex": ft})
	_persist.refold()
	return dot


func _bounds(b: int) -> AABB:
	var d: Dictionary = _level.get_brush(b)
	return AABB(d["aabb_min"], (d["aabb_max"] as Vector3) - (d["aabb_min"] as Vector3))


func _count() -> int:
	return int(_level.get_brush_count())


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		_dock = root.get_node("/root/BuildDock")
		for k: String in PINNED:
			_saved[k] = _dock._prefs.get(k, null)
			_dock._prefs.erase(k)
		_dock.set_pref("snap", true)
		_dock.set_pref("grid", 5)
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		return false
	if _frame < 30:
		return false
	_run()
	return _finish()


func _run() -> void:
	_level = _map.get_node("DCLevel")
	_persist = _map.get("_persist")
	var keet: Node = root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"
	var gs: Node = root.get_node_or_null("/root/GameState")
	if gs != null:
		gs.route("world:level:*", 1, "peer")
	root.get_node("/root/GameMode").set_mode("build")
	_stack = _map.call("_ensure_ops")
	_hist = _map.get("_history")
	_p(_hist != null and _hist.list() != null, "the map registered the History panel")

	# §13 (2026-10-12): the create tool makes PARAMETRIC shapes — one shape.create, its
	# params editable forever in the Shape panel, so no Adjust Last. That is checked first;
	# the multi-record gesture and Adjust Last are then exercised on a 3-brush SCALE (one
	# brush.scale per brush; Steps is adjustable), which is what the stairs used to stand in for.
	print("-- E5a: a parametric stairs is one gesture of one record, no Adjust Last --")
	var bx: CreateBoxOperator = _map.get("_box")
	bx.set_tool_setting("shape", CreateBoxOperator.Shape.STAIRS)
	bx.set_tool_setting("size", 4)
	_place(bx)
	# Headless has no aimed surface, so the box is placed by hand and made the active tool
	# directly; the commit then goes through the REAL OperatorStack.confirm (the gesture).
	_stack._keymap_tool(true)
	_stack.active = bx
	_p(_stack.confirm(), "the stairs commit through the stack")
	_persist.refold()
	var g: Array = _persist.gestures()
	_p(g.size() > 0 and String(g[0]["label"]) == "Add Stairs" and int(g[0]["count"]) == 1,
		"ONE gesture 'Add Stairs' of 1 record (shape.create)", g[0] if g.size() > 0 else "none")
	_p(_stack.adjustable() == null, "no Adjust Last — its params live in the Shape panel", _stack.adjustable())

	print("-- E5a: a 3-brush scale is one gesture --")
	var trio: Array = []
	for i in 3:
		trio.append(_level.brush_by_key(_make(Vector3(50 + i * 3, 0, 0), Vector3(51 + i * 3, 1, 1))))
	var b0: Array = trio.map(_bounds)
	_map.select_brushes(PackedInt32Array(trio))
	_map._unhandled_key_input(_key(KEY_S))          # S since keymap step 3 (was O)
	var sc: Object = _stack.active
	_p(sc is ScaleOperator, "S starts the scale tool on the three", sc)
	if not (sc is ScaleOperator):
		return
	sc.set_tool_setting("pivot", 1)
	sc.set_tool_setting("steps", 1)
	_p(_stack.confirm(), "the scale commits through the stack")
	_persist.refold()
	var b1: Array = trio.map(_bounds)
	g = _persist.gestures()
	_p(g.size() > 0 and int(g[0]["count"]) == 3 and String(g[0]["label"]) == String(sc.gesture_label()),
		"ONE gesture '%s' of 3 records" % sc.gesture_label(), g[0] if g.size() > 0 else "none")

	print("-- E5b: adjust last --")
	_p(_stack.adjustable() == sc and _stack.adjust_valid(), "the scale is adjustable")
	_p(_hist.adjust_bar() != null and _dock.has_panel("adjust_last"), "the Adjust Last panel is up")
	var old_g := String(_stack.adjust_gesture())
	_p(_stack.adjust("steps", 3), "Steps 1 -> 3 re-runs")
	_persist.refold()
	var b3: Array = trio.map(_bounds)
	_p((b3[0] as AABB).size.x > (b1[0] as AABB).size.x + 0.01, "the brushes grew further",
		"%s -> %s" % [(b1[0] as AABB).size, (b3[0] as AABB).size])
	g = _persist.gestures()
	_p(String(g[0]["g"]) != old_g and not bool(g[0]["undone"]) and int(g[0]["count"]) == 3,
		"a NEW live gesture of 3", g[0])
	var old_e: Dictionary = {}
	for e: Dictionary in _persist.gestures(true):
		if String(e["g"]) == old_g:
			old_e = e
	_p(not old_e.is_empty() and bool(old_e["undone"]), "the original stairs gesture is undone (kept in the journal)")
	var offered := false
	for e: Dictionary in g:
		offered = offered or String(e["g"]) == old_g
	_p(not offered, "and DISCARDED from history: the adjusted one replaced it")
	_p(_stack.adjust_valid(), "still adjustable after adjusting")

	print("-- E5a: one Ctrl+Z / one redo --")
	var same := func(a: Array, b: Array) -> bool:
		for i in a.size():
			if not (a[i] as AABB).is_equal_approx(b[i] as AABB):
				return false
		return true
	_map._undo_redo(false)
	_p(same.call(trio.map(_bounds), b0), "ONE undo puts all three back", trio.map(_bounds))
	_p(not _dock.has_panel("adjust_last"), "the undo ended the Adjust Last offer")
	_map._undo_redo(true)
	_p(same.call(trio.map(_bounds), b3), "ONE redo scales all three again (3 steps)")
	_map._undo_redo(true)
	_p(same.call(trio.map(_bounds), b3), "a second redo does NOT resurrect the replaced 1-step scale")

	print("-- E5c: a two-brush rotate is one gesture --")
	_a = _level.brush_by_key(_make(Vector3(30, 0, 0), Vector3(32, 1, 1)))
	_b = _level.brush_by_key(_make(Vector3(34, 0, 0), Vector3(36, 1, 1)))
	var ba := _bounds(_a)
	var bb := _bounds(_b)
	_map.select_brushes(PackedInt32Array([_a, _b]))
	_map._unhandled_key_input(_key(KEY_R))
	for i in 6:
		_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	_map._unhandled_key_input(_key(KEY_ENTER))
	_p(_stack.active == null and not _bounds(_a).is_equal_approx(ba), "Enter rotated the pair")
	g = _persist.gestures()
	_p(String(g[0]["label"]) == "Rotate" and int(g[0]["count"]) == 2, "one gesture 'Rotate' of 2", g[0])
	_map._undo_redo(false)
	_p(_bounds(_a).is_equal_approx(ba) and _bounds(_b).is_equal_approx(bb), "ONE undo restores both brushes")

	print("-- E5d: a record outside any gesture --")
	var dot := _make(Vector3(40, 0, 0), Vector3(41, 1, 1))
	g = _persist.gestures()
	_p(String(g[0]["g"]) == dot and int(g[0]["count"]) == 1 and String(g[0]["label"]) == "Create",
		"is a gesture of one, named by its kind", g[0])

	print("-- E5e: history --")
	_hist.refresh()
	var rows: Array = _hist.rows()
	var labels: Array = rows.map(_label_of)
	_p(not labels.has("Rotate · 2"), "the rotate undone BEFORE that create is discarded (a new edit ends the redo branch)",
		str(labels))
	_p(labels.slice(0, 4) == ["Create", "Create", "Create", "Scale · 3"], "newest first, with counts", str(labels))
	_map._undo_redo(false)                   # undo the newest create: it stays on the redo branch
	_hist.refresh()
	rows = _hist.rows()
	_p(String(_hist.row_text(0)) == "Create" and bool(rows[0]["undone"]), "an undone gesture is listed and marked")
	_map._undo_redo(true)
	_hist.refresh()
	rows = _hist.rows()
	var stairs_row := -1
	for i in rows.size():
		if String(rows[i]["label"]) == "Scale" and not bool(rows[i]["undone"]):
			stairs_row = i
	_p(stairs_row > 1 and String(_hist.row_text(stairs_row)) == "Scale · 3", "the scale row", _hist.row_text(stairs_row))
	# Counted, not looked up: brush_by_key still answers for a brush whose create is undone.
	var c_all := _count()
	_p(_hist.seek_row(stairs_row) == 3, "seek to the scale changes the 3 newer gestures")
	_p(_count() == c_all - 3, "the three newer creates are gone, the scale stays", c_all - _count())
	_p(bool((_persist.gestures() as Array)[0]["undone"]), "and the history marks them undone")
	_hist.seek_row(0)
	_p(_count() == c_all, "seek to the newest row redoes them", _count())
	_p(_bounds(_a).is_equal_approx(ba), "but not the discarded rotate")

	print("-- flatten from the History panel --")
	var fm: Button = _hist.get("_flatten_map_btn")
	_p(fm != null and _hist.get("_flatten_room_btn") != null, "the panel has Flatten map / Flatten room")
	keet.current_role = "peer"
	_hist.refresh()
	_p(fm != null and fm.disabled, "a peer cannot flatten (it erases every author's records)")
	_hist.ask_flatten("map")
	_p(String(_hist.get("_confirm_action")) == "", "and asking does nothing")
	keet.current_role = "admin"
	_hist.refresh()
	_p(fm != null and not fm.disabled, "the admin can")
	var c_before := _count()
	_hist.ask_flatten("map")
	_p(String(_hist.get("_confirm_action")) == "map" and String((_hist.get("_confirm") as ConfirmationDialog).dialog_text).contains("records"),
		"asking opens the confirmation with the record count")
	_p(not (_persist.gestures() as Array).is_empty(), "nothing flattened before confirming")
	_hist.confirm_flatten()
	_p((_persist.gestures() as Array).is_empty() and _hist.rows().is_empty(), "confirmed: History is empty", _hist.rows().size())
	_p(_count() == c_before, "and the level is unchanged", _count() - c_before)
	root.get_node("/root/GameMode").set_mode("play")


func _label_of(e: Dictionary) -> String:
	var n := int(e["count"])
	return String(e["label"]) + ((" · %d" % n) if n > 1 else "")


func _place(bx: CreateBoxOperator) -> void:
	bx.set("_origin", Vector3(20, 0, 20))
	bx.set("_normal", Vector3.UP)
	bx.set("_axis", 1)
	bx.set("_view", Vector3(0, 0, 1))
	bx.set("_valid", true)


func _finish() -> bool:
	for k: String in PINNED:
		if _saved.get(k) == null:
			_dock._prefs.erase(k)
		else:
			_dock._prefs[k] = _saved[k]
	_dock._save_layout()
	if checks < 24:
		_p(false, "only %d checks ran" % checks)
	print("GATE editor_e5: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
	quit(0 if ok else 1)
	return true