OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_brain_params.gd

110 lines · gd ref:
extends SceneTree
## Gate: a brain takes parameters from its declaration.
##
## BotRegistry handed ScriptHost a context of exactly {"bot_id": id}, so every
## published brain was hard-coded: builder.gd's MAX_BLOCKS was a const, and
## wanting a different count meant editing the brain rather than the
## registration. The brain dictionary is the natural place — it already carries
## kind and path — so it gains an optional `params`, merged into the context
## ScriptHost already applies with set().
##
## ⛔ bot_id IS APPLIED LAST AND WINS. params arrive from an admin:cmd, so a
## caller could otherwise pass {"bot_id": "someone-else"} and have the brain act
## as another bot -- move() and despawn() are keyed on it.

var ok := true
var _ran := false


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


func _process(_delta: float) -> bool:
	if _ran:
		return true
	_ran = true
	_run()
	print("GATE brain_params: %s" % ("PASS" if ok else "FAIL"))
	if not ok:
		quit(1)
	return true


func _run() -> void:
	var br: Node = root.get_node_or_null("BotRegistry")
	var sh: Node = root.get_node_or_null("ScriptHost")
	_p(br != null and sh != null, "BotRegistry and ScriptHost autoloads present")
	if br == null or sh == null:
		return

	var real_state: Object = br.state
	br.state = load("res://scenes/test/permissive_state.gd").new()

	# ---- params reach the brain ----
	var r: Dictionary = br.register("p_bot", ["session"], "none", {
		"kind": "script_file",
		"path": "res://scenes/test/fixture_brain.gd",
		"params": {"tuned": 42},
	})
	_p(bool(r.get("ok", false)), "a brain with params registers (%s)" % str(r.get("error", "")))

	var inst: Variant = sh.get_instance("p_bot") if sh.has_method("get_instance") else null
	if inst == null:
		# ScriptHost keeps them privately; reach the loaded record the same way
		# _process does.
		var loaded: Dictionary = sh.get("_loaded") as Dictionary
		if loaded.has("p_bot"):
			inst = (loaded["p_bot"] as Dictionary).get("instance")
	_p(inst != null, "the brain instance is reachable")
	if inst != null:
		_p(int((inst as Object).get("tuned")) == 42,
			"the declared param reached it (tuned=%s)" % str((inst as Object).get("tuned")))
		_p(str((inst as Object).get("bot_id")) == "p_bot",
			"and bot_id still arrived (%s)" % str((inst as Object).get("bot_id")))

	br.despawn("p_bot")

	# ---- bot_id cannot be overridden by a caller ----
	br.register("p_safe", ["session"], "none", {
		"kind": "script_file",
		"path": "res://scenes/test/fixture_brain.gd",
		"params": {"bot_id": "somebody-else"},
	})
	var loaded2: Dictionary = sh.get("_loaded") as Dictionary
	var inst2: Variant = (loaded2.get("p_safe", {}) as Dictionary).get("instance")
	_p(inst2 != null, "the second brain loaded")
	if inst2 != null:
		_p(str((inst2 as Object).get("bot_id")) == "p_safe",
			"params CANNOT hijack bot_id (%s)" % str((inst2 as Object).get("bot_id")))
	br.despawn("p_safe")

	# ---- and the builder honours a count ----
	var builder: GDScript = load("res://bots/builder.gd")
	_p(builder != null, "builder.gd loads")
	if builder != null:
		var b: Object = builder.new()
		_p(b.get("count") != null, "builder declares a `count` property, not a const")
		var dflt: Variant = b.get("count")
		_p(dflt != null and int(dflt) > 0, "with a sane default (%s)" % str(dflt))
		# And the param must actually override it, which is the point.
		b.set("count", 3)
		_p(int(b.get("count")) == 3, "and count is settable, so params can override it")

	# SELF-CHECK: a brain given NO params must still load and keep its defaults,
	# or the merge would be making params mandatory.
	br.register("p_none", ["session"], "none", {
		"kind": "script_file", "path": "res://scenes/test/fixture_brain.gd",
	})
	var loaded3: Dictionary = sh.get("_loaded") as Dictionary
	var inst3: Variant = (loaded3.get("p_none", {}) as Dictionary).get("instance")
	_p(inst3 != null, "self-check: a brain with no params still loads")
	if inst3 != null:
		_p(int((inst3 as Object).get("tuned")) == 0,
			"self-check: and keeps its own default (tuned=%s)" % str((inst3 as Object).get("tuned")))
	br.despawn("p_none")

	br.state = real_state