extends RefCounted ## Authored scenario content added to match the PLAN lists: Joke Shop access ## edge cases, Studio filming boundaries and celebrity crowd spikes, and Building ## events that point at the body-camera / request-reason verbs. const Factory := preload("res://src/content/scenario_factory.gd") const Rules := preload("res://src/core/rules.gd") const Strings := preload("res://src/core/strings.gd") const AccessLoadout := preload("res://src/core/access_loadout.gd") const AccessVerifier := preload("res://src/core/access_verifier.gd") const NpcProfile := preload("res://src/core/npc_profile.gd") const ShiftCoordinator := preload("res://src/core/shift_coordinator.gd") func _events(scenario_id: String) -> Array: var scenario: Resource match scenario_id: "joke_shop": scenario = Factory.joke_shop() "tv_studio": scenario = Factory.tv_studio() _: scenario = Factory.hollywood_building() return scenario.event_pool func _event(scenario_id: String, id: String) -> Dictionary: for event in _events(scenario_id): if str(event.get("id", "")) == id: return event return {} func test_joke_shop_has_the_missing_edge_cases() -> bool: var a := TestUtils.check(not _event("joke_shop", "joke_guest_reservation_no_id").is_empty(), "reservation list without ID exists") var b := TestUtils.check(not _event("joke_shop", "joke_borrowed_id").is_empty(), "borrowed/mismatched ID exists") var c := TestUtils.check(not _event("joke_shop", "joke_refuses_wand").is_empty(), "guest who refuses the wand exists") var d := TestUtils.check(not _event("joke_shop", "joke_late_arrival").is_empty(), "late arrival during the performance exists") return a and b and c and d func test_reservation_without_id_is_denied() -> bool: var event := _event("joke_shop", "joke_guest_reservation_no_id") var profile := _guest_profile() var artifacts := AccessLoadout.build(profile, Factory.joke_shop().access_policy, event.get("override_access", {})) var verifier := AccessVerifier.new() var result: Dictionary = verifier.verify(artifacts, Factory.joke_shop().access_policy, false) var a := TestUtils.check(not result.get("granted", true), "a reservation without ID is not admitted") var b := TestUtils.check(not (result.get("issues", []) as Array).is_empty(), "the denial names the missing artifact") return a and b func test_borrowed_id_is_mismatched() -> bool: var event := _event("joke_shop", "joke_borrowed_id") var profile := _guest_profile() var artifacts := AccessLoadout.build(profile, Factory.joke_shop().access_policy, event.get("override_access", {})) var found_mismatch := false for artifact in artifacts: if artifact.kind == Rules.AccessKind.ID and artifact.status == Rules.ArtifactStatus.MISMATCHED: found_mismatch = true var verifier := AccessVerifier.new() var result: Dictionary = verifier.verify(artifacts, Factory.joke_shop().access_policy, false) var a := TestUtils.check(found_mismatch, "the ID is flagged mismatched") var b := TestUtils.check(not result.get("granted", true), "a mismatched ID is not admitted") return a and b func test_refuses_wand_event_sets_the_flag() -> bool: var event := _event("joke_shop", "joke_refuses_wand") var ok := TestUtils.check(bool(event.get("refuses_screening", false)), "the event marks the refusal") var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd") var wired := TestUtils.check(src.contains("refuses_screening = bool(event.get("), "the spawn copies the flag onto the controller") var handled := TestUtils.check(src.contains("if controller.refuses_screening:"), "the wand screen handles a refusal") return ok and wired and handled func test_studio_filming_boundary_repeats() -> bool: var first := _event("tv_studio", "studio_film_boundary") var repeat := _event("tv_studio", "studio_film_boundary_repeat") var a := TestUtils.check(not first.is_empty(), "first boundary event exists") var b := TestUtils.check(not repeat.is_empty(), "repeat boundary event exists") var c := TestUtils.check_eq(str(first.get("intent", "")), "film_boundary", "first uses the boundary intent") var d := TestUtils.check_eq(str(repeat.get("intent", "")), "film_boundary", "repeat uses the boundary intent") var e := TestUtils.check(int(repeat.get("time_range_s", Vector2.ZERO).x) > int(first.get("time_range_s", Vector2.ZERO).x), "the repeat happens later in the shift") return a and b and c and d and e func test_film_boundary_stands_at_the_steps() -> bool: var scenario := Factory.tv_studio() var spot: Vector3 = scenario.waypoint_for_intent("film_boundary") var sidewalk: Vector3 = scenario.waypoint_for_intent("film") var a := TestUtils.check(spot != sidewalk, "the boundary spot differs from the permitted sidewalk spot") var b := TestUtils.check_eq(spot, scenario._location_or_default("steps_end", Vector3.ZERO), "the influencer stands at the steps, past the line") return a and b func test_celebrity_arrival_spawns_a_crowd() -> bool: var event := _event("tv_studio", "studio_celebrity_arrival") var extras: Array = event.get("extra_spawns", []) var a := TestUtils.check(extras.size() >= 3, "celebrity arrival adds several bystanders") var types: Array = [] for extra in extras: types.append(int((extra as Dictionary).get("npc_type", -1))) var b := TestUtils.check(types.has(Rules.NpcType.TOURIST), "tourists gather") var c := TestUtils.check(types.has(Rules.NpcType.INFLUENCER), "an influencer films it") var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd") var d := TestUtils.check(src.contains('event.get("extra_spawns", [])'), "the spawner honours extra_spawns") return a and b and c and d func test_building_events_point_at_the_new_verbs() -> bool: var camera_event := _event("hollywood_building", "building_filming_boundary") var reason_event := _event("hollywood_building", "building_unclear_intent") var a := TestUtils.check(str(camera_event.get("hint", "")).contains("body"), "an event hints at the body camera") var b := TestUtils.check(str(reason_event.get("hint", "")).contains("reason"), "an event hints at requesting a reason") var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd") var c := TestUtils.check(src.contains('if event.has("hint")'), "spawning logs the authored hint") return a and b and c func test_every_authored_hint_resolves() -> bool: var missing: Array[String] = [] for scenario in [Factory.hollywood_building(), Factory.joke_shop(), Factory.tv_studio()]: for event in scenario.event_pool: var key := str((event as Dictionary).get("hint", "")) if key != "" and Strings.lookup(key) == key: missing.append(key) return TestUtils.check(missing.is_empty(), "all authored hints have strings (missing: %s)" % str(missing)) func test_hollywood_night_has_routine_traffic() -> bool: var scenario := Factory.hollywood_building() var late_routine := 0 for event in scenario.event_pool: if int(event.get("phase", -1)) == Rules.Phase.LATE \ and str(event.get("kind", "routine")) == "routine": late_routine += 1 var a := TestUtils.check(late_routine >= 3, "the night shift has a late routine crowd (got %d)" % late_routine) var coordinator: ShiftCoordinator = ShiftCoordinator.new() coordinator.setup(scenario, 900.0, 7) coordinator.phase_offset_s = 700.0 coordinator.start() var b := TestUtils.check_eq(coordinator.current_phase, Rules.Phase.LATE, "the night offset starts in LATE") var c := TestUtils.check(is_equal_approx(coordinator.schedule_time_s(), 700.0), "the scheduler sees the late time window while the clock starts at zero") return a and b and c func test_new_events_have_unique_ids() -> bool: # Event ids only need to be unique within a venue. var problems: Array[String] = [] for scenario in [Factory.hollywood_building(), Factory.joke_shop(), Factory.tv_studio()]: var seen := {} for event in scenario.event_pool: var id := str((event as Dictionary).get("id", "")) if seen.has(id): problems.append("%s:%s" % [scenario.id, id]) seen[id] = true return TestUtils.check(problems.is_empty(), "event ids are unique per venue (dupes: %s)" % str(problems)) func _guest_profile() -> NpcProfile: var p := NpcProfile.new() p.id = "guest" p.display_name = "Guest" p.npc_type = Rules.NpcType.AUTHORIZED_GUEST return p