feat(client): pure MoveValidator — membership is the whole test
This commit is contained in:
29
client/scripts/npc/move_validator.gd
Normal file
29
client/scripts/npc/move_validator.gd
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
class_name MoveValidator
|
||||||
|
extends RefCounted
|
||||||
|
## Pure §6 validation: an emitted move is legal iff its canonical key is in the
|
||||||
|
## turn's available_moves (which NpcContent already computed from live state).
|
||||||
|
## No state reads here — membership is the whole test. Invalid moves are not an
|
||||||
|
## error; they are dropped and returned separately so the caller can log them.
|
||||||
|
|
||||||
|
const ID_MOVES := ["reveal", "offer_quest", "give_item", "accept_item"]
|
||||||
|
const BARE_MOVES := ["adjust_disposition", "refuse", "end_conversation", "become_hostile"]
|
||||||
|
|
||||||
|
|
||||||
|
static func _key(move: Dictionary) -> String:
|
||||||
|
var name: String = move.get("name", "")
|
||||||
|
var args: Array = move.get("args", [])
|
||||||
|
if name in ID_MOVES:
|
||||||
|
return "%s(%s)" % [name, str(args[0])] if not args.is_empty() else name
|
||||||
|
return name # bare/value moves key on the name alone
|
||||||
|
|
||||||
|
|
||||||
|
static func validate(moves: Array, available_moves: Array) -> Dictionary:
|
||||||
|
var valid: Array = []
|
||||||
|
var dropped: Array = []
|
||||||
|
for move in moves:
|
||||||
|
var name: String = move.get("name", "")
|
||||||
|
if (name in ID_MOVES or name in BARE_MOVES) and _key(move) in available_moves:
|
||||||
|
valid.append(move)
|
||||||
|
else:
|
||||||
|
dropped.append(move)
|
||||||
|
return {"valid": valid, "dropped": dropped}
|
||||||
38
client/tests/unit/test_move_validator.gd
Normal file
38
client/tests/unit/test_move_validator.gd
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
extends "res://addons/gut/test.gd"
|
||||||
|
|
||||||
|
const MoveValidator = preload("res://scripts/npc/move_validator.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func _m(name, args := []) -> Dictionary:
|
||||||
|
return {"name": name, "args": args}
|
||||||
|
|
||||||
|
|
||||||
|
func test_id_move_in_available_is_valid():
|
||||||
|
var res = MoveValidator.validate(
|
||||||
|
[_m("reveal", ["varrell_twins"])], ["reveal(varrell_twins)", "refuse"])
|
||||||
|
assert_eq(res["valid"].size(), 1)
|
||||||
|
assert_eq(res["dropped"].size(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_id_move_not_in_available_is_dropped():
|
||||||
|
var res = MoveValidator.validate(
|
||||||
|
[_m("offer_quest", ["some_other_quest"])], ["offer_quest(find_the_ledger)"])
|
||||||
|
assert_eq(res["valid"].size(), 0)
|
||||||
|
assert_eq(res["dropped"].size(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
func test_bare_move_keys_on_name():
|
||||||
|
var res = MoveValidator.validate([_m("refuse")], ["refuse"])
|
||||||
|
assert_eq(res["valid"].size(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
func test_adjust_disposition_keys_on_name_keeps_delta():
|
||||||
|
var res = MoveValidator.validate(
|
||||||
|
[_m("adjust_disposition", ["+5"])], ["adjust_disposition"])
|
||||||
|
assert_eq(res["valid"].size(), 1)
|
||||||
|
assert_eq(res["valid"][0]["args"], ["+5"])
|
||||||
|
|
||||||
|
|
||||||
|
func test_unknown_move_name_is_dropped():
|
||||||
|
var res = MoveValidator.validate([_m("cast_fireball", ["9"])], ["refuse"])
|
||||||
|
assert_eq(res["dropped"].size(), 1)
|
||||||
Reference in New Issue
Block a user