Files
Ransom-s-wake/tests/unit/run_sonar_application_tests.gd
T
2026-10-06 19:40:10 +02:00

192 lines
6.7 KiB
GDScript

extends SceneTree
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
const SonarScanState = preload("res://game/domain/sonar/sonar_scan_state.gd")
const SonarSample = preload("res://game/domain/sonar/sonar_sample.gd")
const SonarSampleBuffer = preload(
"res://game/domain/sonar/sonar_sample_buffer.gd"
)
const SonarCaptureRequest = preload(
"res://game/application/sonar/sonar_capture_request.gd"
)
const SonarCaptureSample = preload(
"res://game/application/sonar/sonar_capture_sample.gd"
)
const SonarCaptureResult = preload(
"res://game/application/sonar/sonar_capture_result.gd"
)
const SonarAcquisitionPort = preload(
"res://game/application/interfaces/sonar_acquisition_port.gd"
)
const SonarService = preload("res://game/application/sonar/sonar_service.gd")
const TEST_COUNT := 34
var _failures: Array[String] = []
class RecordingAcquisition extends SonarAcquisitionPort:
var requests: Array[SonarCaptureRequest] = []
func capture(request: SonarCaptureRequest) -> SonarCaptureResult:
requests.append(request)
var samples: Array[SonarCaptureSample] = [
SonarCaptureSample.new(
0, 0, Vector3(-2.0, -7.0, 8.0), Vector3.UP, 0.9, -7.0,
SonarMaterial.Value.METAL
),
SonarCaptureSample.new(
0, 1, Vector3(-1.0, -7.0, 8.0), Vector3.UP, 0.8, -7.0,
SonarMaterial.Value.METAL
),
SonarCaptureSample.new(
5, 5, Vector3(12.0, -11.0, 24.0), Vector3.UP, 1.0, -11.0,
SonarMaterial.Value.METAL
),
SonarCaptureSample.new(
2, 2, Vector3(0.0, -14.0, 16.0), Vector3.UP, 0.6, -14.0,
SonarMaterial.Value.ROCK
),
]
return SonarCaptureResult.new(true, samples)
func _init() -> void:
_test_sample_buffer_contract()
_test_service_lifecycle_and_contacts()
if _failures.is_empty():
print("Sonar application unit tests: %d passed, 0 failed." % TEST_COUNT)
quit(0)
else:
for failure: String in _failures:
push_error(failure)
print("Sonar application unit tests: %d failed." % _failures.size())
quit(1)
func _test_sample_buffer_contract() -> void:
var sample := SonarSample.new(
Vector3(1.0, -3.0, 8.0),
Vector3.UP,
0.75,
-3.0,
SonarMaterial.Value.ROCK,
true
)
_expect(sample.validation_error().is_empty(), "observed typed sample is valid")
var sand_sample := SonarSample.new(
Vector3(2.0, -4.0, 9.0),
Vector3.UP,
0.4,
-4.0,
SonarMaterial.Value.SAND,
true
)
_expect(sand_sample.validation_error().is_empty(), "sand is a valid terrain material")
_expect(SonarMaterial.is_valid(SonarMaterial.Value.SAND), "sand is inside the material range")
var buffer := SonarSampleBuffer.new(3, 2)
_expect(buffer.set_sample(1, 2, sample).is_empty(), "sample writes inside buffer bounds")
_expect(buffer.observed_count() == 1, "buffer counts observed cells")
var restored: SonarSample = buffer.sample_at(1, 2)
_expect(
restored.observed and is_equal_approx(restored.intensity, 0.75),
"typed accessor restores aligned sample fields"
)
_expect(not buffer.sample_at(4, 4).observed, "out-of-range accessor returns unknown sample")
_expect(buffer.seal().is_empty(), "valid sample buffer seals")
_expect(
buffer.set_sample(0, 0, sample) == "sonar sample buffer is sealed",
"sealed sample buffer rejects mutation"
)
_expect(buffer.validation_error().is_empty(), "sealed aligned buffer is publishable")
func _test_service_lifecycle_and_contacts() -> void:
var acquisition := RecordingAcquisition.new()
var service := SonarService.new(acquisition, &"test-dive")
var first = service.request_scan(Transform3D.IDENTITY)
_expect(first.accepted, "manual sonar request is accepted while ready")
_expect(
acquisition.requests.size() == 1
and acquisition.requests[0].columns == 49
and acquisition.requests[0].rows == 31,
"acquisition receives baseline grid dimensions"
)
_expect(
is_equal_approx(acquisition.requests[0].maximum_range_m, 95.0)
and is_equal_approx(rad_to_deg(acquisition.requests[0].horizontal_opening_radians), 76.0),
"acquisition receives baseline range and opening"
)
_expect(first.scan.validation_error().is_empty(), "published sonar scan is valid")
_expect(first.scan.samples.observed_count() == 4, "capture becomes a compact sealed buffer")
_expect(first.scan.contacts.size() == 2, "separated metal groups become distinct contacts")
_expect(
first.scan.contacts[0].sample_count == 2
and first.scan.contacts[1].sample_count == 1,
"adjacent metal samples cluster deterministically"
)
_expect(service.visible_row_count() == 0, "scan has no immediate visible return")
_expect(not service.request_scan(Transform3D.IDENTITY).accepted, "request during reveal is rejected")
service.advance(0.1)
_expect(service.visible_row_count() == 1, "first return appears at four percent")
_expect(
service.current_scan().state == SonarScanState.Value.REVEALING,
"scan remains in reveal after its first return"
)
service.advance(2.4)
_expect(
service.current_scan().state == SonarScanState.Value.RECOVERY,
"completed reveal enters recovery"
)
_expect(service.previous_scan() == null, "previous scan is released after replacement")
_expect(not service.request_scan(Transform3D.IDENTITY).accepted, "request during recovery is rejected")
service.advance(0.35)
_expect(service.current_scan().state == SonarScanState.Value.READY, "recovery returns sonar to ready")
var second = service.request_scan(Transform3D.IDENTITY)
_expect(second.accepted, "next manual sonar request is accepted")
_expect(
service.previous_scan().scan_id == first.scan.scan_id,
"one previous scan is retained"
)
_expect(
is_equal_approx(service.previous_scan_opacity(), 0.52),
"previous scan starts at baseline opacity"
)
service.advance(1.25)
_expect(
is_equal_approx(service.current_scan().reveal_progress, 0.5),
"reveal advances from explicit elapsed time"
)
_expect(
service.previous_scan_opacity() > 0.0
and service.previous_scan_opacity() < 0.52,
"previous scan fades during replacement"
)
_expect(
service.visible_row_count() > 1 and service.visible_row_count() < 31,
"partial reveal exposes only near rows"
)
service.advance(1.25)
_expect(
service.current_scan().state == SonarScanState.Value.RECOVERY,
"second reveal completes after 2.5 seconds"
)
_expect(service.previous_scan() == null, "faded previous scan is discarded")
service.advance(0.35)
_expect(service.current_scan().state == SonarScanState.Value.READY, "second recovery completes")
_expect(first.scan.scan_id != second.scan.scan_id, "scan identifiers are unique and ordered")
_expect(acquisition.requests.size() == 2, "rejected commands do not invoke acquisition")
_expect(
is_equal_approx(first.scan.maximum_elevation_m - first.scan.minimum_elevation_m, 7.0),
"scan retains a real elevation scale wider than the minimum span"
)
func _expect(condition: bool, label: String) -> void:
if not condition:
_failures.append(label)