feat: bound active regional presentation
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
extends GutTest
|
||||
|
||||
const WORLD_ID := &"regional_bosnia"
|
||||
const LOCATION_ID := &"location_jajce"
|
||||
const CONTEXT_ID := &"jajce_surface"
|
||||
|
||||
|
||||
func test_two_thousand_candidates_stay_bounded_pinned_and_order_independent() -> void:
|
||||
var policy := PresentationRelevancePolicy.new()
|
||||
var candidates := _large_candidate_fixture()
|
||||
var first := policy.select_detailed_visuals(
|
||||
_context(), _address(LOCATION_ID, Vector3.ZERO), candidates
|
||||
)
|
||||
|
||||
assert_true(first["valid"])
|
||||
assert_eq(first["candidate_count"], 2000)
|
||||
assert_eq(first["selected_ids"].size(), PresentationRelevancePolicy.DEFAULT_DETAIL_LIMIT)
|
||||
assert_eq(first["mandatory_count"], 6)
|
||||
for mandatory_id in _mandatory_ids():
|
||||
assert_has(first["selected_ids"], mandatory_id)
|
||||
var decision := policy.get_decision(first, mandatory_id)
|
||||
assert_true(decision["selected"])
|
||||
assert_true(decision["mandatory"])
|
||||
assert_true("selected_mandatory_pin" in decision["reason_trace"])
|
||||
|
||||
var reordered := candidates.duplicate(true)
|
||||
for candidate: Dictionary in reordered:
|
||||
candidate["loaded"] = not bool(candidate["loaded"])
|
||||
reordered.reverse()
|
||||
var second := policy.select_detailed_visuals(
|
||||
_context(), _address(LOCATION_ID, Vector3.ZERO), reordered
|
||||
)
|
||||
|
||||
assert_true(second["valid"])
|
||||
assert_eq(second["selected_ids"], first["selected_ids"])
|
||||
assert_eq(_selected_scores(second), _selected_scores(first))
|
||||
|
||||
|
||||
func test_context_and_location_scope_override_equal_coordinates_and_foreign_pins() -> void:
|
||||
var policy := PresentationRelevancePolicy.new()
|
||||
var candidates: Array[Dictionary] = [
|
||||
_candidate(&"local_named", LOCATION_ID, Vector3.ZERO, 0.2),
|
||||
_candidate(
|
||||
&"foreign_threat",
|
||||
&"location_travnik",
|
||||
Vector3.ZERO,
|
||||
1.0,
|
||||
{"player_affecting_threat": true}
|
||||
),
|
||||
_candidate(
|
||||
&"other_context_leader",
|
||||
LOCATION_ID,
|
||||
Vector3.ZERO,
|
||||
1.0,
|
||||
{"context_id": &"jajce_interior", "leader": true}
|
||||
),
|
||||
]
|
||||
var result := policy.select_detailed_visuals(
|
||||
_context(), _address(LOCATION_ID, Vector3.ZERO), candidates, 2
|
||||
)
|
||||
|
||||
assert_true(result["valid"])
|
||||
assert_eq(result["selected_ids"], [&"local_named"])
|
||||
var foreign := policy.get_decision(result, &"foreign_threat")
|
||||
assert_false(foreign["selected"])
|
||||
assert_has(foreign["reason_trace"], "rejected_different_location")
|
||||
assert_has(foreign["reason_trace"], "pin_ignored_outside_active_scope")
|
||||
assert_has(foreign["reason_trace"], "pin_player_affecting_threat")
|
||||
var other_context := policy.get_decision(result, &"other_context_leader")
|
||||
assert_has(other_context["reason_trace"], "rejected_inactive_context")
|
||||
|
||||
|
||||
func test_score_distance_and_stable_id_form_a_total_tie_order_with_rejection_trace() -> void:
|
||||
var policy := PresentationRelevancePolicy.new()
|
||||
var candidates: Array[Dictionary] = [
|
||||
_candidate(&"entity_b", LOCATION_ID, Vector3(3.0, 0.0, 0.0), 0.5),
|
||||
_candidate(&"entity_a", LOCATION_ID, Vector3(3.0, 0.0, 0.0), 0.5),
|
||||
_candidate(&"entity_far", LOCATION_ID, Vector3(20.0, 0.0, 0.0), 0.1),
|
||||
]
|
||||
var result := policy.select_detailed_visuals(
|
||||
_context(), _address(LOCATION_ID, Vector3.ZERO), candidates, 1
|
||||
)
|
||||
|
||||
assert_true(result["valid"])
|
||||
assert_eq(result["selected_ids"], [&"entity_a"])
|
||||
var rejected_tie := policy.get_decision(result, &"entity_b")
|
||||
assert_false(rejected_tie["selected"])
|
||||
assert_true(_trace_contains_prefix(rejected_tie["reason_trace"], "importance_units_"))
|
||||
assert_true(_trace_contains_prefix(rejected_tie["reason_trace"], "distance_millimeters_"))
|
||||
assert_true(
|
||||
_trace_contains_prefix(rejected_tie["reason_trace"], "rejected_detail_capacity_rank_")
|
||||
)
|
||||
|
||||
candidates.reverse()
|
||||
var reordered := policy.select_detailed_visuals(
|
||||
_context(), _address(LOCATION_ID, Vector3.ZERO), candidates, 1
|
||||
)
|
||||
assert_eq(reordered["selected_ids"], result["selected_ids"])
|
||||
|
||||
|
||||
func test_unsatisfiable_pin_overflow_fails_closed_instead_of_silently_unpinning() -> void:
|
||||
var policy := PresentationRelevancePolicy.new()
|
||||
var candidates: Array[Dictionary] = [
|
||||
_candidate(&"leader_a", LOCATION_ID, Vector3.ZERO, 0.1, {"leader": true}),
|
||||
_candidate(&"leader_b", LOCATION_ID, Vector3.ONE, 0.2, {"leader": true}),
|
||||
_candidate(&"leader_c", LOCATION_ID, Vector3.RIGHT, 0.3, {"leader": true}),
|
||||
]
|
||||
var result := policy.select_detailed_visuals(
|
||||
_context(), _address(LOCATION_ID, Vector3.ZERO), candidates, 2
|
||||
)
|
||||
|
||||
assert_false(result["valid"])
|
||||
assert_true(result["selected_ids"].is_empty())
|
||||
assert_eq(result["mandatory_count"], 3)
|
||||
assert_true("mandatory_pin_overflow" in String(result["errors"][0]))
|
||||
for candidate in candidates:
|
||||
var decision := policy.get_decision(result, candidate["stable_id"])
|
||||
assert_has(decision["reason_trace"], "rejected_unsatisfiable_pin_overflow")
|
||||
|
||||
|
||||
func test_navigation_request_budget_grants_only_two_new_stable_requests_per_frame() -> void:
|
||||
var budget := PresentationNavRequestBudget.new()
|
||||
assert_true(budget.enqueue(&"entity_c", 5, ["relevant"]))
|
||||
assert_true(budget.enqueue(&"entity_b", 10, ["threat"]))
|
||||
assert_true(budget.enqueue(&"entity_a", 10, ["leader"]))
|
||||
assert_true(budget.enqueue(&"entity_d", 1, ["nearby"]))
|
||||
assert_false(budget.enqueue(&"entity_a", 99, ["duplicate"]))
|
||||
|
||||
var first_frame := budget.take_new_requests(42)
|
||||
assert_eq(_request_ids(first_frame), [&"entity_a", &"entity_b"])
|
||||
assert_eq(first_frame.size(), PresentationNavRequestBudget.MAX_NEW_REQUESTS_PER_FRAME)
|
||||
assert_true(budget.take_new_requests(42).is_empty())
|
||||
assert_eq(budget.get_in_flight_ids(), [&"entity_a", &"entity_b"])
|
||||
assert_eq(budget.get_pending_ids(), [&"entity_c", &"entity_d"])
|
||||
|
||||
var next_frame := budget.take_new_requests(43)
|
||||
assert_eq(_request_ids(next_frame), [&"entity_c", &"entity_d"])
|
||||
assert_true(budget.complete(&"entity_a"))
|
||||
assert_true(budget.enqueue(&"entity_a", 2, ["retargeted"]))
|
||||
assert_eq(_request_ids(budget.take_new_requests(44)), [&"entity_a"])
|
||||
|
||||
|
||||
func _large_candidate_fixture() -> Array[Dictionary]:
|
||||
var candidates: Array[Dictionary] = []
|
||||
for index in 2000:
|
||||
var extras := {}
|
||||
match index:
|
||||
1994:
|
||||
extras["pinned"] = true
|
||||
1995:
|
||||
extras["player_affecting_threat"] = true
|
||||
1996:
|
||||
extras["situation_participant"] = true
|
||||
1997:
|
||||
extras["commitment_participant"] = true
|
||||
1998:
|
||||
extras["leader"] = true
|
||||
1999:
|
||||
extras["unique_owner"] = true
|
||||
var position := Vector3(float(index % 73), 0.0, float((index * 11) % 89))
|
||||
var importance := float((index * 37) % 1000) / 999.0
|
||||
var candidate := _candidate(
|
||||
StringName("named_entity_%04d" % index), LOCATION_ID, position, importance, extras
|
||||
)
|
||||
candidate["loaded"] = index % 3 == 0
|
||||
candidates.append(candidate)
|
||||
return candidates
|
||||
|
||||
|
||||
func _mandatory_ids() -> Array[StringName]:
|
||||
return [
|
||||
&"named_entity_1994",
|
||||
&"named_entity_1995",
|
||||
&"named_entity_1996",
|
||||
&"named_entity_1997",
|
||||
&"named_entity_1998",
|
||||
&"named_entity_1999",
|
||||
]
|
||||
|
||||
|
||||
func _candidate(
|
||||
stable_id: StringName,
|
||||
location_id: StringName,
|
||||
position: Vector3,
|
||||
importance: float,
|
||||
extras: Dictionary = {}
|
||||
) -> Dictionary:
|
||||
var descriptor := {
|
||||
"stable_id": stable_id,
|
||||
"context_id": CONTEXT_ID,
|
||||
"address": _address(location_id, position),
|
||||
"importance": importance,
|
||||
"named": true,
|
||||
"loaded": false,
|
||||
}
|
||||
for field in extras:
|
||||
descriptor[field] = extras[field]
|
||||
return descriptor
|
||||
|
||||
|
||||
func _context() -> Dictionary:
|
||||
return {"context_id": CONTEXT_ID, "world_id": WORLD_ID, "location_id": LOCATION_ID}
|
||||
|
||||
|
||||
func _address(location_id: StringName, position: Vector3) -> SpatialAddress:
|
||||
return SpatialAddress.create(WORLD_ID, location_id, position)
|
||||
|
||||
|
||||
func _selected_scores(result: Dictionary) -> Array[int]:
|
||||
var scores: Array[int] = []
|
||||
for decision: Dictionary in result["selected_details"]:
|
||||
scores.append(int(decision["score_units"]))
|
||||
return scores
|
||||
|
||||
|
||||
func _request_ids(requests: Array[Dictionary]) -> Array[StringName]:
|
||||
var ids: Array[StringName] = []
|
||||
for request in requests:
|
||||
ids.append(StringName(request["stable_id"]))
|
||||
return ids
|
||||
|
||||
|
||||
func _trace_contains_prefix(trace: Array, prefix: String) -> bool:
|
||||
for reason in trace:
|
||||
if String(reason).begins_with(prefix):
|
||||
return true
|
||||
return false
|
||||
Reference in New Issue
Block a user