Files
TheTree_mock/tests/staff/test_staff_commute.gd
T

318 lines
20 KiB
GDScript

extends SceneTree
const CommuteType = preload("res://scripts/staff/staff_commute_service.gd")
var _checks := 0
var _failures := 0
var _fixtures: Array = []
## Movement is injected at Runtime's narrow boundary. Real Staff, Shift, Task,
## Facility and staff-only Path rights run unchanged in this focused suite.
class CommuteRuntime extends RefCounted:
var staff_service := StaffService.new()
var facility_service := FacilityService.new()
var path_service := PathService.new()
var commute = CommuteType.new()
var movements: Array[Dictionary] = []
var last_moved: Dictionary = {}
var fail_movement := false
var in_transit := false
func configure() -> void:
var cells: Array[Vector3i] = []
for layer: int in [0, 1]:
for x in range(13): cells.append(Vector3i(x, 0, layer))
path_service.rebuild_graph(cells)
path_service.configure_connector(&"staff_door", Vector3i.ZERO, Vector3i(0, 0, 1), 100, true, &"staff")
for row: Array in [[&"west", Vector3i(5, 0, 0)], [&"east", Vector3i(10, 0, 0)], [&"back", Vector3i(5, 0, 1)]]:
facility_service.create_instance({&"instance_id": row[0], &"definition_id": &"office", &"origin": row[1], &"occupied_cells": [row[1]], &"staff_min": 1, &"staff_max": 2})
commute.configure(self)
func advance_staff_commute(staff_id: StringName, target: Vector3i, minute: int, reason: StringName = &"") -> Dictionary:
var worker := staff_service.get_staff(staff_id)
if not reason.is_empty() or int(last_moved.get(staff_id, -1)) >= minute or fail_movement:
return {&"moved": false}
var path := path_service.find_path(worker.current_cell, target, [0, 1], [&"staff"])
if path.size() <= 1: return {&"moved": false}
var previous := worker.current_cell
worker.current_cell = path[1]
last_moved[staff_id] = minute
movements.append({&"minute": minute, &"staff_id": staff_id, &"from": previous, &"to": worker.current_cell})
return {&"moved": true}
func is_staff_in_transit(_staff_id: StringName) -> bool:
return in_transit
func advance(minute: int) -> void:
staff_service.advance_one_minute(minute)
commute.tick(minute)
for facility: FacilityInstance in facility_service.get_instances():
var active: Array[StringName] = []
for worker: StaffRuntime in staff_service.get_staff_members():
if commute.is_present(worker, facility): active.append(worker.staff_id)
facility_service.set_operational_status(facility.instance_id, {&"active_staff_ids": active}, minute)
func dispose() -> void:
staff_service.configure_attendance_driver()
commute._runtime = null
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("P2: physical attendance, recurring commutes and saved position replay")
_test_legacy_contract()
_test_walk_and_arrival_staffing()
_test_early_arrival_and_shift_end()
_test_late_route_repair_and_replay()
_test_reassignment_and_return_to_post()
_test_reassignment_after_late_arrival_counts_boundary_once()
_test_porter_dispatch_after_check_in()
_test_back_layer_permissions_and_unavailable()
_test_night_shift_and_missing_workplace()
_test_boarded_reassignment_does_not_staff_origin()
for fixture: CommuteRuntime in _fixtures: fixture.dispose()
print("Staff commute: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _fixture(role: StringName = &"receptionist", minute := 360) -> CommuteRuntime:
var fixture := CommuteRuntime.new()
fixture.configure()
fixture.staff_service.create_staff(&"worker", role)
fixture.staff_service.advance_one_minute(minute)
_fixtures.append(fixture)
return fixture
func _plan(fixture: CommuteRuntime, facility: StringName = &"west", template: StringName = &"DAY") -> StaffRuntime:
_expect(fixture.staff_service.set_work_assignment(&"worker", facility, template).is_success(), "repeating work plan is accepted")
return fixture.staff_service.get_staff(&"worker")
func _test_legacy_contract() -> void:
var legacy := StaffService.new()
legacy.create_staff(&"legacy", &"receptionist")
legacy.advance_one_minute(360)
legacy.set_work_assignment(&"legacy", &"west", &"DAY")
_expect(legacy.get_staff(&"legacy").state == StaffRuntime.State.AVAILABLE, "component-only recurring plans keep their original shift contract")
var fixture := _fixture()
fixture.staff_service.create_standard_shift(&"worker", &"DAY", 0, &"west", &"manual")
var worker := fixture.staff_service.get_staff(&"worker")
_expect(worker.state == StaffRuntime.State.AVAILABLE and not fixture.staff_service.requires_attendance(&"worker"), "manual legacy shifts remain available without a persistent plan")
_expect(fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"west")), "legacy manual-shift staffing keeps compatibility")
func _test_walk_and_arrival_staffing() -> void:
var fixture := _fixture()
var worker := _plan(fixture)
var shift := fixture.staff_service.get_shift(worker.shift_id)
var west := fixture.facility_service.get_instance(&"west")
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i.ZERO, "paused assignment starts a real commute without teleporting")
_expect(shift.worked_minutes == 0 and shift.actual_started_at == -1, "assignment time is not falsely counted as work")
_expect(not fixture.commute.is_present(worker, west), "a remote assignment cannot staff a facility")
for minute in range(361, 365):
var previous := worker.current_cell
fixture.advance(minute)
_expect(worker.current_cell.distance_to(previous) <= 1.0, "commute advances at most one route edge per minute")
_expect(worker.current_cell == Vector3i(4, 0, 0), "worker reaches the actual facility access cell")
_expect(worker.state == StaffRuntime.State.COMMUTING and west.active_staff_ids.is_empty(), "movement arrival activates at the next Staff AI boundary")
fixture.advance(365)
_expect(worker.state == StaffRuntime.State.AVAILABLE and west.active_staff_ids == [&"worker"], "only physical arrival contributes the facility staff capacity")
_expect(shift.actual_started_at == 365 and shift.worked_minutes == 1, "first recorded work minute is the arrival activation minute")
var snapshot := fixture.staff_service.create_snapshot()
fixture.commute.get_report()
_expect(fixture.staff_service.create_snapshot() == snapshot, "attendance report reads do not mutate saved Staff state")
_expect(fixture.staff_service.validate_invariants()[&"ok"], "physical commute preserves Staff invariants")
func _test_early_arrival_and_shift_end() -> void:
var fixture := _fixture(&"receptionist", 330)
var worker := _plan(fixture)
var shift := fixture.staff_service.get_shift(worker.shift_id)
for minute in range(331, 360): fixture.advance(minute)
_expect(worker.current_cell == Vector3i(4, 0, 0) and worker.state == StaffRuntime.State.COMMUTING, "early arrival waits at the workplace through the commute lead window")
_expect(shift.actual_started_at == -1 and shift.worked_minutes == 0, "early arrival does not start paid duty before 06:00")
fixture.advance(360)
_expect(worker.state == StaffRuntime.State.AVAILABLE and shift.actual_started_at == 360, "an employee already at the workplace starts at exactly 06:00")
fixture.advance(1080)
_expect(worker.state == StaffRuntime.State.OFF_DUTY and worker.shift_id.is_empty() and shift.actual_ended_at == 1080, "18:00 closes the work shift and removes attendance")
_expect(worker.current_cell == Vector3i(4, 0, 0), "shift end keeps the real position rather than teleporting home")
fixture.advance(1770)
_expect(worker.state == StaffRuntime.State.COMMUTING, "next day's recurring commute begins automatically at 05:30")
fixture.advance(1800)
_expect(worker.state == StaffRuntime.State.AVAILABLE, "next day's saved plan activates only at its new shift boundary")
func _test_late_route_repair_and_replay() -> void:
var fixture := _fixture()
fixture.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), true)
var worker := _plan(fixture)
for minute in range(361, 379): fixture.advance(minute)
var shift := fixture.staff_service.get_shift(worker.shift_id)
_expect(worker.current_cell == Vector3i.ZERO and worker.state == StaffRuntime.State.COMMUTING, "a blocked route never teleports or invents attendance")
_expect(shift.late_minutes == 18 and shift.late_reason == CommuteType.ROUTE_BLOCKED, "lateness records the real route blockage after the grace threshold")
_expect(shift.worked_minutes == 0 and shift.actual_started_at == -1, "unreachable staff do not accumulate fictional worked minutes")
var checkpoint := fixture.staff_service.create_snapshot()
var restored := _fixture()
_expect(restored.staff_service.restore_snapshot(checkpoint), "a blocked commute snapshot restores using existing Staff and Shift data")
restored.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), true)
_expect(restored.staff_service.create_snapshot() == checkpoint, "restored blocked attendance is exact without new service state")
fixture.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), false)
restored.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), false)
for minute in range(379, 390):
fixture.advance(minute)
restored.advance(minute)
_expect(fixture.staff_service.create_snapshot() == restored.staff_service.create_snapshot(), "route repair replays to identical work, lateness and positions after restore")
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.absence_reason.is_empty(), "repairing the route allows physical arrival and clears the active absence")
_expect(shift.actual_started_at == 383 and shift.late_minutes == 22, "arrival keeps historical late minutes while beginning real duty")
var midwalk := _fixture()
var walker := _plan(midwalk)
midwalk.advance(361)
midwalk.advance(362)
var moving := midwalk.staff_service.create_snapshot()
var replay := _fixture()
_expect(replay.staff_service.restore_snapshot(moving), "mid-walk save restores the exact current cell")
for minute in range(363, 368):
midwalk.advance(minute)
replay.advance(minute)
_expect(midwalk.staff_service.create_snapshot() == replay.staff_service.create_snapshot() and walker.current_cell == Vector3i(4, 0, 0), "mid-walk replay neither skips nor repeats route cells")
func _test_reassignment_and_return_to_post() -> void:
var fixture := _fixture()
var worker := _plan(fixture)
for minute in range(361, 367): fixture.advance(minute)
var first := fixture.staff_service.get_shift(worker.shift_id)
_expect(fixture.staff_service.set_work_assignment(&"worker", &"east", &"DAY").is_success(), "fixed staff can be reassigned after arriving")
first = fixture.staff_service.get_shift(first.shift_instance_id)
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(4, 0, 0), "reassignment walks from the current location")
_expect(first.assigned_facility_id == &"west" and first.actual_ended_at == 366, "historical duty remains attached to the old workplace")
for minute in range(367, 374): fixture.advance(minute)
var shift := fixture.staff_service.get_shift(worker.shift_id)
_expect(worker.current_cell == Vector3i(9, 0, 0) and worker.state == StaffRuntime.State.AVAILABLE, "reassignment opens the new workplace after arrival")
# Completion of a portable task leaves its employee at the work target.
worker.current_cell = Vector3i(6, 0, 0)
worker.transition_to(StaffRuntime.State.AVAILABLE, &"TASK_COMPLETED", 374)
fixture.advance(374)
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(7, 0, 0), "fixed roles return to their post after work elsewhere")
fixture.advance(375)
fixture.advance(376)
fixture.advance(377)
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.current_cell == Vector3i(9, 0, 0), "desk service resumes after returning physically")
_expect(shift.late_minutes == 0, "returning from a completed task does not rewrite arrival lateness")
_expect(fixture.staff_service.set_work_assignment(&"worker", &"", &"DAY").is_success(), "unassignment cancels the repeating commute")
var cell := worker.current_cell
fixture.advance(378)
_expect(worker.state == StaffRuntime.State.OFF_DUTY and worker.current_cell == cell, "unassigned staff keep position and stop commuting")
func _test_reassignment_after_late_arrival_counts_boundary_once() -> void:
var fixture := _fixture()
var worker := _plan(fixture)
for minute in range(361, 366): fixture.advance(minute)
var old_id := worker.shift_id
fixture.facility_service.create_instance({&"instance_id": &"nearby", &"definition_id": &"office", &"origin": Vector3i(3, 0, 0), &"occupied_cells": [Vector3i(3, 0, 0)]})
_expect(fixture.staff_service.get_shift(old_id).worked_minutes == 1, "late arrival has only one actual duty minute before reassignment")
_expect(fixture.staff_service.set_work_assignment(&"worker", &"nearby", &"DAY").is_success(), "paused reassignment may choose another workplace sharing this access cell")
var old := fixture.staff_service.get_shift(old_id)
var replacement := fixture.staff_service.get_shift(worker.shift_id)
_expect(old.worked_minutes == 0 and old.actual_started_at == -1 and replacement.worked_minutes == 1, "reassignment transfers the current duty minute exactly once despite earlier commute gaps")
_expect(fixture.staff_service.restore_snapshot(fixture.staff_service.create_snapshot()), "gap-aware historical shift split restores without extra saved attendance state")
func _test_porter_dispatch_after_check_in() -> void:
var fixture := _fixture(&"porter")
var worker := _plan(fixture)
_expect(not worker.is_available_for_dispatch(), "new porter cannot take a delivery before checking in")
for minute in range(361, 367): fixture.advance(minute)
worker.current_cell = Vector3i(10, 0, 0)
worker.transition_to(StaffRuntime.State.AVAILABLE, &"TASK_COMPLETED", 367)
fixture.advance(367)
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.current_cell == Vector3i(10, 0, 0), "checked-in porter can accept another job from a delivery destination")
_expect(not fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"west")), "porter away from the warehouse does not falsely staff the warehouse")
var restored := _fixture(&"porter")
_expect(restored.staff_service.restore_snapshot(fixture.staff_service.create_snapshot()), "the porter's once-per-shift check-in survives save restoration")
restored.advance(368)
_expect(restored.staff_service.get_staff(&"worker").state == StaffRuntime.State.AVAILABLE, "loaded porter duty does not force an unnecessary return commute")
fixture.advance(1080)
fixture.advance(1770)
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(9, 0, 0), "the following shift requires a fresh real warehouse check-in")
func _test_back_layer_permissions_and_unavailable() -> void:
var fixture := _fixture()
var worker := _plan(fixture, &"back")
var access: Dictionary = fixture.commute.find_facility_access_path(Vector3i.ZERO, fixture.facility_service.get_instance(&"back"))
_expect(access[&"reachable"] and access[&"cell"].z == 1, "commute can reach BACK facilities through an authorized staff door")
_expect(fixture.path_service.find_path(Vector3i.ZERO, Vector3i(4, 0, 1), [0, 1], []).is_empty(), "the same staff door remains inaccessible without staff rights")
fixture.advance(361)
_expect(worker.current_cell == Vector3i(0, 0, 1), "cross-layer commute uses the actual connector endpoint")
worker.health = 0
worker.transition_to(StaffRuntime.State.INCAPACITATED, &"TEST", 362)
fixture.advance(362)
_expect(worker.current_cell == Vector3i(0, 0, 1) and worker.state == StaffRuntime.State.INCAPACITATED, "unavailable workers do not walk or get promoted to available")
worker.health = 100
worker.transition_to(StaffRuntime.State.COMMUTING, &"RECOVERED", 363)
fixture.fail_movement = true
fixture.advance(363)
_expect(worker.current_cell == Vector3i(0, 0, 1), "a refused Runtime movement leaves the commute position unchanged")
fixture.fail_movement = false
for minute in range(364, 371): fixture.advance(minute)
_expect(fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"back")), "recovered staff finish the real BACK commute")
_expect(not CommuteType.is_at_facility(worker, fixture.facility_service.get_instance(&"west")), "identical screen coordinates on different layers never count as attendance")
var upper := FacilityInstance.new().configure(&"upper", &"office", {&"origin": Vector3i(4, 1, 1), &"occupied_cells": [Vector3i(4, 1, 1)]})
_expect(not CommuteType.is_at_facility(worker, upper), "standing below an upper room cannot bypass the route to its floor")
_expect(not fixture.commute.find_facility_access_path(worker.current_cell, upper)[&"reachable"], "a corridor underneath a room is not a valid workplace entrance")
func _test_night_shift_and_missing_workplace() -> void:
var fixture := _fixture(&"receptionist", 1410)
var worker := _plan(fixture, &"west", &"NIGHT")
for minute in range(1411, 1417): fixture.advance(minute)
var shift := fixture.staff_service.get_shift(worker.shift_id)
fixture.advance(1440)
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.shift_id == shift.shift_instance_id, "midnight retains the same physically attended NIGHT shift")
fixture.advance(1800)
_expect(worker.state == StaffRuntime.State.OFF_DUTY and shift.actual_ended_at == 1800, "NIGHT attendance ends at next-day 06:00")
var missing := _fixture()
var absent := _plan(missing, &"missing")
missing.advance(361)
_expect(absent.state == StaffRuntime.State.COMMUTING and absent.absence_reason == CommuteType.WORKPLACE_MISSING, "missing workplace remains a visible waiting condition")
_expect(missing.staff_service.get_shift(absent.shift_id).worked_minutes == 0, "a missing workplace creates no phantom duty")
func _test_boarded_reassignment_does_not_staff_origin() -> void:
var fixture := _fixture()
var worker := _plan(fixture)
fixture.facility_service.create_instance({&"instance_id": &"boarding_post", &"definition_id": &"office", &"origin": Vector3i.ZERO, &"occupied_cells": [Vector3i.ZERO]})
fixture.in_transit = true
_expect(fixture.staff_service.set_work_assignment(&"worker", &"boarding_post", &"DAY").is_success(), "a boarded commute can accept a new destination plan")
var shift := fixture.staff_service.get_shift(worker.shift_id)
fixture.advance(361)
_expect(worker.state == StaffRuntime.State.COMMUTING and shift.worked_minutes == 0, "the cabin's projected boarding cell cannot turn into attendance at the origin")
_expect(not fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"boarding_post")), "a passenger never supplies staff capacity while still in transit")
_expect(fixture.commute.get_report()[&"staff"][0][&"reason"] == &"ELEVATOR_WAIT", "commute report distinguishes the owned ride from arrival at its projected origin")
_expect(worker.current_cell == Vector3i.ZERO and fixture.movements.is_empty(), "the commute driver waits for the owned ride rather than moving its projected anchor")
fixture.in_transit = false
worker.current_cell = Vector3i(3, 0, 0)
fixture.advance(362)
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(2, 0, 0), "after safe disembarkation the employee walks toward the latest assignment")
fixture.advance(363)
fixture.advance(364)
_expect(worker.state == StaffRuntime.State.AVAILABLE and shift.actual_started_at == 364, "new-post duty begins only after the completed ride and actual return")
fixture.in_transit = true
worker.current_task_id = &"owned_work"
_expect(fixture.commute.can_attend_shift(worker, shift, 365), "an already checked-in employee's owned portable work remains duty while riding")
worker.current_task_id = &""
_expect(not fixture.commute.can_attend_shift(worker, shift, 365), "an idle passenger cannot become dispatchable based on its old checked-in position")
func _expect(condition: bool, message: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error("FAIL: %s" % message)