Files
security-game/src/test/tests/test_door_and_flashlight.gd
T
zwitschi 16dc1dfea4
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
Tune the flashlight against measured luminance and refresh the venue gallery
2026-11-06 20:20:22 +01:00

82 lines
4.0 KiB
GDScript

extends RefCounted
## Headless tests for the door lock mechanic and flashlight perception bonus.
const Rules := preload("res://src/core/rules.gd")
const DoorState := preload("res://src/core/door_state.gd")
const NpcProfile := preload("res://src/core/npc_profile.gd")
const NPCController := preload("res://src/core/npc_controller.gd")
func test_door_toggle_transitions() -> bool:
var door := DoorState.new()
var a := TestUtils.check_eq(door.locked, false, "starts unlocked")
var b := TestUtils.check_eq(door.toggle(), true, "toggle locks")
var c := TestUtils.check_eq(door.toggle(), false, "toggle unlocks")
return a and b and c
func test_locked_door_thwarts_force() -> bool:
var door := DoorState.new()
door.set_locked(true)
var result := door.attempt_force()
var a := TestUtils.check_eq(result["succeeded"], false, "locked door blocks forced entry")
var b := TestUtils.check_eq(result["outcome"], "thwarted", "thwarted outcome")
return a and b
func test_unlocked_door_admits_force() -> bool:
var door := DoorState.new()
var result := door.attempt_force()
var a := TestUtils.check_eq(result["succeeded"], true, "open door lets forced entry through")
var b := TestUtils.check_eq(result["outcome"], "entered", "entered outcome")
return a and b
func test_flashlight_aims_a_tight_forward_spot() -> bool:
## The lamp sits on the guard's head and aims straight down the view axis with
## a narrow cone: a wide cone washed the whole entrance instead of lighting the
## door being checked (realism pass, 2026-11-01).
var root := Node3D.new()
var gm: Node = load("res://src/game/game_manager.gd").new()
root.add_child(gm)
var guard: Node3D = gm.call("_build_guard")
var lamp: SpotLight3D = guard.get_node_or_null("Head/Flashlight") as SpotLight3D
var a := TestUtils.check(lamp != null, "flashlight lives on the guard's head")
# The lamp is tilted a few degrees down so the beam also puts a pool on the
# pavement in front; straight ahead left that ground black. Anything past ~8 deg
# drops the door being checked out of the cone.
var b := TestUtils.check(lamp != null
and lamp.rotation_degrees.x <= 0.0 and lamp.rotation_degrees.x >= -8.0
and is_zero_approx(lamp.rotation_degrees.y) and is_zero_approx(lamp.rotation_degrees.z),
"flashlight aims forward with a slight downward tilt (x %.1f)" % (
lamp.rotation_degrees.x if lamp else 0.0))
# Measured: the old 4.2 energy / 20 m / 14 deg beam changed the late-night frame by
# 0.1/255 and read as off. These bounds keep it a torch (not a floodlight) while
# still lighting what the guard looks at.
var c := TestUtils.check(lamp != null and lamp.spot_angle >= 20.0
and lamp.spot_angle <= 30.0,
"flashlight cone reads as a torch (%.0f deg)" % (lamp.spot_angle if lamp else 0.0))
var c2 := TestUtils.check(lamp != null and lamp.light_energy >= 8.0
and lamp.spot_range >= 30.0,
"flashlight is bright enough to see (energy %.1f, range %.0f m)" % [
lamp.light_energy if lamp else 0.0, lamp.spot_range if lamp else 0.0])
var d := TestUtils.check(lamp != null and not lamp.visible, "flashlight starts off")
var e := TestUtils.check(lamp != null
and is_zero_approx(lamp.light_volumetric_fog_energy),
"headless/compatibility builds leave the flashlight non-volumetric")
root.free()
return a and b and c and c2 and d and e
func test_flashlight_widens_perception() -> bool:
## Without the flashlight, an NPC at AWARE_RADIUS + 3 m is unaware.
## With the flashlight bonus (4 m), the same distance registers as aware.
var p := NpcProfile.new()
p.id = "nightwalker"
p.npc_type = Rules.NpcType.TOURIST
var dim := NPCController.new()
dim.setup("d1", p, "wander", 1)
var unlit: Dictionary = dim.perceive_guard(NPCController.AWARE_RADIUS + 3.0, 0.0)
var a := TestUtils.check_eq(unlit["band"], "unaware", "unlit: too far to notice")
var lit := NPCController.new()
lit.setup("l1", p, "wander", 2)
var lit_result: Dictionary = lit.perceive_guard(NPCController.AWARE_RADIUS + 3.0, 4.0)
var b := TestUtils.check_eq(lit_result["band"], "aware", "flashlight bonus makes NPC aware")
return a and b