231 lines
19 KiB
GDScript
231 lines
19 KiB
GDScript
extends "res://tests/s3/test_runtime_actor_hardening.gd"
|
|
var _observations: Array[Dictionary] = []
|
|
var _receipts: Array[Dictionary] = []
|
|
var _first_receipts: Array[Dictionary] = []
|
|
var _receipt_hook: Callable
|
|
var _evidence_dir := ""
|
|
var _facility_id: StringName = &""
|
|
var _service_id: StringName = &""
|
|
var _request_id: StringName = &"p04.order.midnight.request"
|
|
var _guest_id: StringName = &"p04.order.midnight.guest"
|
|
var _staff_ids: Array[StringName] = [&"p04.midnight.server.a", &"p04.midnight.server.b"]
|
|
var _serial := 0
|
|
var _case := "midnight"
|
|
var _original_price := 0
|
|
var _observation_pass := "first"
|
|
var _first_summary: Dictionary = {}
|
|
var _paid_reception_id: StringName = &""
|
|
var _expected_daily_wages := 0
|
|
var _duplicate_results: Array[Dictionary] = []
|
|
|
|
func _run() -> void:
|
|
_evidence_dir = OS.get_environment("THE_TREE_ORDER_EVIDENCE")
|
|
if _evidence_dir.is_empty(): quit(2); return
|
|
_expect(DirAccess.make_dir_recursive_absolute(_evidence_dir) == OK, "fresh external evidence directory")
|
|
_run_midnight()
|
|
_finish_order()
|
|
|
|
func _run_midnight() -> void:
|
|
_reset_runtime(424242)
|
|
var enabled: Dictionary = _runtime.enable_production_operations()
|
|
_expect(bool(enabled.get(&"ok", false)), "public production operations enabled at original Day 1 06:00")
|
|
if not bool(enabled.get(&"ok", false)): return
|
|
var facility := _build_cafeteria(true)
|
|
if facility == null: return
|
|
_facility_id = facility.instance_id
|
|
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility.definition_id)
|
|
_original_price = definition.base_price
|
|
_setup_paid_reception()
|
|
var cook_id := StringName("fixture.kitchen.worker.%s" % _facility_id)
|
|
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": cook_id, &"template_id": &"NIGHT", &"facility_id": facility.related_facility_id}).is_success(), "public original NIGHT assignment replaces inherited 23:59 cook fixture")
|
|
for id: StringName in _staff_ids:
|
|
_expect(_cmd(&"register_staff", {&"staff_id": id, &"role_id": &"server", &"options": {&"state": StaffRuntime.State.OFF_DUTY, &"current_cell": Vector3i.ZERO}}).is_success(), "register real workplace Staff " + String(id))
|
|
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": id, &"template_id": &"NIGHT", &"facility_id": _facility_id}).is_success(), "original NIGHT 18:00-next06:00 assignment " + String(id))
|
|
var queue_at := 1440 - definition.base_service_minutes - 1
|
|
_expect(queue_at > _clock.absolute_minute, "setup finishes before original service start")
|
|
if queue_at <= _clock.absolute_minute: return
|
|
_clock.advance_minutes(1079 - _clock.absolute_minute)
|
|
_observe("paid-before-handover-17-59")
|
|
_expect(_runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_akane"), "normally hired daytime receptionist physically attends before original DAY boundary")
|
|
_clock.register_tick_handler(_clock.TickPhase.FACILITY_COMPLETION, &"zz.p04.paid.before-handover", func(minute: int):
|
|
if minute == 1080:
|
|
_observe("paid-at-completion-before-handover-18-00")
|
|
_expect(_runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_akane"), "last DAY interval retains actual hired worker before handover")
|
|
)
|
|
_clock.advance_one_minute(); _observe("paid-after-handover-18-00")
|
|
_expect(not _runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_akane"), "daytime hired receptionist is removed after completion phase")
|
|
_clock.advance_minutes(queue_at - _clock.absolute_minute)
|
|
_expect(_runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_bardo"), "normally hired NIGHT replacement physically arrives")
|
|
_expect(_clock.absolute_minute == queue_at and _runtime.operations_coordinator.current_minute == queue_at, "real production operations advance without blocked suffix")
|
|
if _clock.absolute_minute != queue_at: return
|
|
for id: StringName in _staff_ids:
|
|
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
|
_expect(_runtime.staff_service.requires_attendance(id) and StaffCommuteService.is_at_facility(worker, facility) and worker.current_cell != Vector3i.ZERO, "physical ordinary NIGHT workplace arrival " + String(id))
|
|
var kitchen: FacilityInstance = _runtime.facility_service.get_instance(facility.related_facility_id)
|
|
var cook: StaffRuntime = _runtime.staff_service.get_staff(cook_id)
|
|
_expect(_runtime.staff_service.requires_attendance(cook_id) and StaffCommuteService.is_at_facility(cook, kitchen), "real kitchen NIGHT attendance, not legacy attendance bypass")
|
|
_expect(_stock(facility.local_inventory_id, &"ingredient/basic_meal", 1, &"p04.midnight.stock"), "one real meal to reserve")
|
|
_expect(_cmd(&"spawn_guest", {&"guest_id": _guest_id, &"guest_type_id": &"traveler", &"gold": _original_price * 2, &"options": {&"state": GuestRuntime.State.DECIDING, &"current_cell": Vector3i.ZERO}}).is_success(), "public actual Guest wallet")
|
|
_expect(_target_request().is_success(), "original public service request with explicit idempotency key")
|
|
_assert_request_resend("queued-same-minute")
|
|
_clock.advance_one_minute()
|
|
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
|
|
_expect(active.size() == 1, "one actual reserved service starts")
|
|
if active.size() != 1: return
|
|
var service: ServiceInstance = active[0]; _service_id = service.service_id
|
|
_expect(service.started_at + service.duration_minutes == 1440 and service.duration_minutes == definition.base_service_minutes and service.price_snapshot == _original_price, "original service completion coincides with 00:00")
|
|
_clock.advance_minutes(1439 - _clock.absolute_minute)
|
|
_expect(service.elapsed_minutes == service.duration_minutes - 1, "one original service minute remains at 23:59")
|
|
_expect(_runtime.operations_coordinator.get_day_summary(1).is_empty(), "real Day 1 not closed early")
|
|
_expect(_cmd(&"set_facility_policy", {&"facility_id": kitchen.instance_id, &"patch": {&"business_open": false}}).is_success(), "public dependency kitchen closure; dining itself remains open by policy")
|
|
_expect(not facility.related_facility_operating and kitchen.state == FacilityInstance.State.CLOSED, "dependency closure projects to dining")
|
|
var held_ids: Array[StringName] = service.inventory_reservations.duplicate()
|
|
var held_elapsed := service.elapsed_minutes
|
|
var held_service_id := service.service_id
|
|
_connect_receipts(); _observe("closed-before-23-59")
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
_clock.advance_one_minute(); _observe("paused-00-00")
|
|
_expect(_clock.absolute_minute == 1440 and int(_runtime.operations_coordinator.get_day_summary(1).get(&"closed_at", -1)) == 1440, "real daily close proceeds while dependent service is paused")
|
|
_assert_held(service, held_service_id, held_ids, held_elapsed)
|
|
var midnight: Dictionary = _runtime.create_snapshot()
|
|
_first_summary = _runtime.operations_coordinator.get_day_summary(1).duplicate(true)
|
|
_expect(_expected_daily_wages > 0 and int(_first_summary[&"settlement"][&"new_wages_total"]) == _expected_daily_wages and int(_first_summary[&"settlement"][&"paid_total"]) == _expected_daily_wages, "real midnight settles full existing catalog wages once")
|
|
var wage_rows := 0
|
|
for receipt: Dictionary in _receipts:
|
|
for row: Dictionary in receipt[&"rows"]:
|
|
if row[&"category"] == &"WAGE":
|
|
wage_rows += 1
|
|
_expect(receipt[&"minute"] == 1440 and int(row[&"amount"]) == _expected_daily_wages and row[&"credit_account"] == &"PLAYER_CASH", "one real payroll transaction debits actual player cash at midnight")
|
|
_expect(wage_rows == 1, "exactly one nonzero payroll ledger row at midnight")
|
|
_clock.advance_one_minute(); _observe("paused-00-01")
|
|
_assert_held(service, held_service_id, held_ids, held_elapsed)
|
|
_assert_request_resend("paused-00-01")
|
|
var paused: Dictionary = _runtime.create_snapshot()
|
|
_expect(_reopen_dependency(kitchen.instance_id).is_success(), "public reopening of same dependency")
|
|
_clock.advance_one_minute(); _observe("resumed-complete-00-02")
|
|
_assert_completed(service, held_service_id, held_ids)
|
|
_assert_request_resend("completed-00-02")
|
|
var after: Dictionary = _runtime.create_snapshot()
|
|
_clock.advance_one_minute(); _observe("stable-00-03")
|
|
_assert_completed(service, held_service_id, held_ids)
|
|
var next: Dictionary = _runtime.create_snapshot()
|
|
_first_receipts.assign(_receipts.duplicate(true))
|
|
_expect(_runtime.restore_snapshot(before), "restore typed pre-midnight dependency-closed snapshot")
|
|
_observation_pass = "replay"; _receipts.clear(); _connect_receipts()
|
|
_clock.advance_one_minute()
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(midnight), "typed paused midnight state replay matches")
|
|
_clock.advance_one_minute()
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paused), "typed second paused state replay matches")
|
|
_expect(_reopen_dependency(kitchen.instance_id).is_success(), "replay same public reopen command and idempotency key")
|
|
_clock.advance_one_minute()
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(after), "typed completed state replay matches")
|
|
_clock.advance_one_minute()
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(next), "typed subsequent state replay matches")
|
|
_expect(_first_receipts == _receipts, "all actual service and settlement receipts replay unchanged")
|
|
_write_var("before", before); _write_var("midnight", midnight); _write_var("paused", paused); _write_var("after", after); _write_var("next", next)
|
|
|
|
func _target_request() -> CommandResult:
|
|
return _runtime.submit_command(&"create_service_request", {&"facility_id": _facility_id, &"request_id": _request_id, &"guest_ids": [_guest_id]}, &"player", &"", 10, -1, &"p04.same-minute.service.request")
|
|
|
|
func _assert_request_resend(label: String) -> void:
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
var result: CommandResult = _target_request()
|
|
_expect(result.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY, "normal repeated processed request reports its original duplicate-key contract " + label)
|
|
var after: Dictionary = _runtime.create_snapshot()
|
|
_expect(var_to_bytes(before) == var_to_bytes(after), "same-minute request resend preserves all typed Runtime state " + label)
|
|
_write_var("duplicate-" + label + "-before", before)
|
|
_write_var("duplicate-" + label + "-after", after)
|
|
_duplicate_results.append({&"label": label, &"minute": _clock.absolute_minute, &"command_result": result.to_dictionary(), &"request_id": _request_id, &"idempotency_key": &"p04.same-minute.service.request"})
|
|
|
|
func _setup_paid_reception() -> void:
|
|
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(&"reception")
|
|
_expect(_cmd(&"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(2, 0, WorldService.LAYER_FRONT)}).is_success(), "paid public reception construction")
|
|
_clock.advance_minutes(definition.build_minutes)
|
|
for instance: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if instance.definition_id == &"reception": _paid_reception_id = instance.instance_id
|
|
_expect(not _paid_reception_id.is_empty(), "reception completes using unchanged construction duration")
|
|
var catalog: Dictionary = JSON.parse_string(FileAccess.get_file_as_string("res://data/characters/mvp_staff_candidates.json"))
|
|
for candidate_id: StringName in [&"staff_akane", &"staff_bardo"]:
|
|
var original_wage := -1
|
|
for row: Dictionary in catalog["entries"]:
|
|
if StringName(row["id"]) == candidate_id: original_wage = int(row["daily_wage"])
|
|
_expect(original_wage > 0 and _cmd(&"hire_staff_candidate", {&"candidate_id": candidate_id}).is_success(), "public hire of existing paid candidate " + String(candidate_id))
|
|
var worker: StaffRuntime = _runtime.staff_service.get_staff(candidate_id)
|
|
_expect(worker != null and worker.get_effective_daily_wage() == original_wage, "existing catalog wage unchanged for " + String(candidate_id))
|
|
_expected_daily_wages += original_wage
|
|
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": candidate_id, &"template_id": &"DAY" if candidate_id == &"staff_akane" else &"NIGHT", &"facility_id": _paid_reception_id}).is_success(), "ordinary recurring paid DAY or NIGHT assignment")
|
|
|
|
func _reopen_dependency(kitchen_id: StringName) -> CommandResult:
|
|
return _runtime.submit_command(&"set_facility_policy", {&"facility_id": kitchen_id, &"patch": {&"business_open": true}}, &"player", &"", 10, -1, &"p04.dependency.reopen")
|
|
|
|
func _assert_held(service: ServiceInstance, held_id: StringName, ids: Array[StringName], elapsed: int) -> void:
|
|
_expect(service.service_id == held_id and service.elapsed_minutes == elapsed and service.inventory_reservations == ids, "same service and own reservation IDs retain exact progress")
|
|
_expect(_service_receipts().is_empty() and _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(_guest_id))) == _original_price * 2, "no service sale or wallet charge during dependency closure")
|
|
for id: StringName in ids:
|
|
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(id)
|
|
_expect(reservation != null and reservation.state == InventoryReservation.State.ACTIVE, "own reservation remains reserved while dependency is closed")
|
|
|
|
func _assert_completed(service: ServiceInstance, held_id: StringName, ids: Array[StringName]) -> void:
|
|
_expect(service.service_id == held_id and service.state == ServiceInstance.State.COMPLETED and service.completed_at == 1442, "same service completes once after dependency restoration")
|
|
_expect(service.inventory_reservations == ids and _service_receipts().size() == 1 and _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(_guest_id))) == _original_price, "same reserved service has one original-price payment")
|
|
for id: StringName in ids:
|
|
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(id)
|
|
_expect(reservation != null and reservation.state == InventoryReservation.State.CONSUMED, "own original reservation consumed once after restoration")
|
|
_expect(_runtime.operations_coordinator.get_day_summary(1) == _first_summary, "restored dependency does not mutate prior daily close")
|
|
|
|
func _service_receipts() -> Array:
|
|
var selected: Array = []
|
|
for receipt: Dictionary in _receipts:
|
|
for row: Dictionary in receipt[&"rows"]:
|
|
if row[&"category"] == &"SERVICE_SALE" and row[&"source_entity_id"] == _facility_id and row[&"credit_account"] == StringName("GUEST_WALLET:" + String(_guest_id)):
|
|
selected.append(receipt); break
|
|
return selected
|
|
|
|
func _connect_receipts() -> void:
|
|
if _receipt_hook.is_valid() and _runtime.economy_service.transaction_posted.is_connected(_receipt_hook):
|
|
_runtime.economy_service.transaction_posted.disconnect(_receipt_hook)
|
|
_receipt_hook = func(id: StringName, entries: Array):
|
|
var rows: Array = []
|
|
for row: LedgerEntry in entries: rows.append(row.to_dictionary())
|
|
_receipts.append({&"id": id, &"minute": _clock.absolute_minute, &"rows": rows, &"staff_at_receipt": _runtime.facility_service.get_instance(_facility_id).active_staff_ids.duplicate()})
|
|
_runtime.economy_service.transaction_posted.connect(_receipt_hook)
|
|
|
|
func _cmd(kind: StringName, payload: Dictionary) -> CommandResult:
|
|
_serial += 1
|
|
return _runtime.submit_command(kind, payload, &"player", &"", 10, -1, StringName("p04.order.shift.command.%d" % _serial))
|
|
|
|
func _observe(label: String) -> void:
|
|
label = _case + "-" + _observation_pass + "-" + label
|
|
var facility: FacilityInstance = _runtime.facility_service.get_instance(_facility_id)
|
|
var service: ServiceInstance = _runtime.facility_service.get_service(_service_id)
|
|
var guest: GuestRuntime = _runtime.guest_service.get_guest(_guest_id)
|
|
var paid: Array = []
|
|
for paid_id: StringName in [&"staff_akane", &"staff_bardo"]:
|
|
var hired: StaffRuntime = _runtime.staff_service.get_staff(paid_id)
|
|
if hired != null: paid.append(hired.create_snapshot())
|
|
var reception: FacilityInstance = _runtime.facility_service.get_instance(_paid_reception_id)
|
|
_observations.append({&"label": label + "-paid-actors", &"minute": _clock.absolute_minute, &"paid_staff": paid, &"paid_reception": reception.create_snapshot() if reception != null else {}, &"expected_catalog_wages": _expected_daily_wages})
|
|
var workers: Array = []
|
|
for id: StringName in _staff_ids:
|
|
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
|
var shift: ShiftInstance = _runtime.staff_service.get_shift(worker.shift_id)
|
|
workers.append({&"staff": worker.create_snapshot(), &"shift": shift.create_snapshot() if shift != null else {}, &"is_present": _runtime.staff_commute.is_present(worker, facility), &"requires_attendance": _runtime.staff_service.requires_attendance(id), &"at_facility": StaffCommuteService.is_at_facility(worker, facility), &"within_shift": shift.contains_minute(_clock.absolute_minute) if shift != null else false})
|
|
_observations.append({&"label": label, &"minute": _clock.absolute_minute, &"workers": workers, &"facility_state": facility.state, &"power_available": facility.power_available, &"active_staff": facility.active_staff_ids.duplicate(), &"service": service.create_snapshot() if service != null else {}, &"request_present": _runtime.facility_service.get_request(_request_id) != null, &"wallet": _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(_guest_id))), &"available_meals": _runtime.inventory_service.available_quantity(facility.local_inventory_id, &"ingredient/basic_meal"), &"reservations": _runtime.inventory_service.create_snapshot(), &"guest": guest.create_snapshot() if guest != null else {}, &"receipts": _receipts.duplicate(true), &"day_summary": _runtime.operations_coordinator.get_day_summary(1), &"operations": _runtime.operations_coordinator.create_snapshot()})
|
|
_write_var(_observation_pass + "-" + label.trim_prefix(_case + "-" + _observation_pass + "-"), _runtime.create_snapshot())
|
|
|
|
func _write_var(label: String, value: Variant) -> void:
|
|
var file := FileAccess.open(_evidence_dir.path_join(_case + "-" + label + ".var"), FileAccess.WRITE)
|
|
_expect(file != null, "write whole typed evidence " + label)
|
|
if file != null: file.store_buffer(var_to_bytes(value)); file.flush(); file.close()
|
|
var json_file := FileAccess.open(_evidence_dir.path_join(_case + "-" + label + ".json"), FileAccess.WRITE)
|
|
_expect(json_file != null, "write JSON mirror alongside authoritative typed snapshot")
|
|
if json_file != null: json_file.store_string(JSON.stringify(value, "\t")); json_file.flush(); json_file.close()
|
|
|
|
func _finish_order() -> void:
|
|
var file := FileAccess.open(_evidence_dir.path_join("observations.json"), FileAccess.WRITE)
|
|
if file != null:
|
|
file.store_string(JSON.stringify({&"classification": "P04-01 same-minute public request resend before admission, during dependency pause and after completion, with paid catalog DAY/NIGHT handover and nonzero payroll across midnight; catalog values are observed inputs, not approval of final product balancing; restaurant setup remains controlled; typed same-process replay only; no formal SaveService/OS/Windows/whole acceptance", &"observations": _observations, &"first_receipts": _first_receipts, &"replay_receipts": _receipts, &"first_summary": _first_summary, &"duplicate_results": _duplicate_results, &"checks": _checks, &"failures": _failures}, "\t")); file.flush(); file.close()
|
|
_cleanup()
|
|
print("Midnight boundary diagnostic: %d checks, %d failures" % [_checks, _failures])
|
|
quit(1 if _failures else 0)
|