Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
222 lines
14 KiB
GDScript
222 lines
14 KiB
GDScript
extends SceneTree
|
|
|
|
const Pricing = preload("res://scripts/facility/facility_pricing.gd")
|
|
const Facilities = preload("res://scripts/facility/facility_service.gd")
|
|
const Facility = preload("res://scripts/facility/facility_instance.gd")
|
|
const Service = preload("res://scripts/facility/service_instance.gd")
|
|
const Guests = preload("res://scripts/guest/guest_service.gd")
|
|
const Guest = preload("res://scripts/guest/guest_runtime.gd")
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
print("S2: Facility pricing and operating policy")
|
|
_test_prices_and_satisfaction()
|
|
_test_validation_is_atomic()
|
|
_test_locked_quotes_and_scheduled_changes()
|
|
_test_closed_service_and_opening_hours()
|
|
_test_snapshot_restore_and_legacy_policy()
|
|
_test_queue_release_is_atomic()
|
|
if _failures == 0:
|
|
print("PASS: %d checks" % _checks)
|
|
else:
|
|
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
|
quit(_failures)
|
|
|
|
|
|
func _test_prices_and_satisfaction() -> void:
|
|
var rows := Pricing.modes()
|
|
_expect(rows.size() == 5, "five price modes are exposed")
|
|
var expected_prices := [15, 20, 25, 38, 63]
|
|
var expected_deltas := [10, 5, 0, -5, -20]
|
|
var expected_softened := [10, 5, 0, -3, -10]
|
|
for index: int in rows.size():
|
|
var policy := {&"price_mode": rows[index][&"id"]}
|
|
_expect(Pricing.quote(25, policy) == expected_prices[index], "25 G price follows %s policy" % rows[index][&"id"])
|
|
_expect(Pricing.satisfaction_modifier(policy, 69, 50) == expected_deltas[index], "nineteen quality points retain %s penalty" % rows[index][&"id"])
|
|
_expect(Pricing.satisfaction_modifier(policy, 70, 50) == expected_softened[index], "twenty quality points soften %s penalty" % rows[index][&"id"])
|
|
_expect(Pricing.quote(0, policy) == 0, "free service remains free for %s" % rows[index][&"id"])
|
|
rows[0][&"percent"] = 999
|
|
_expect(Pricing.modes()[0][&"percent"] == 60, "mode reports are defensive copies")
|
|
_expect(Pricing.quote(35, {&"price_mode": &"expensive"}) == 53, "half gold rounds up using integer arithmetic")
|
|
_expect(Pricing.quote(3, {"price_mode": "charitable"}) == 2, "JSON string policy keys and IDs quote correctly")
|
|
_expect(Pricing.quote(120, {}) == 120, "missing legacy price mode defaults to standard")
|
|
_expect(Pricing.quote(-20, {}) == 0, "negative base quotations clamp to zero")
|
|
|
|
|
|
func _test_validation_is_atomic() -> void:
|
|
var facilities := _facilities()
|
|
var before := facilities.create_snapshot()
|
|
for invalid: Variant in [&"premium", "", 1, false, {}, []]:
|
|
var result := facilities.set_policy(&"inn", {&"price_mode": invalid, &"vip_priority": true})
|
|
_expect(not result.is_success(), "unsupported price mode is rejected: %s" % str(invalid))
|
|
_expect(facilities.create_snapshot() == before, "invalid price patch is atomic: %s" % str(invalid))
|
|
for invalid: Variant in [0, 1, "false", null]:
|
|
_expect(not facilities.set_policy(&"inn", {&"business_open": invalid}, 25).is_success(), "business_open requires bool: %s" % str(invalid))
|
|
_expect(facilities.create_snapshot() == before, "invalid future policy cannot replace pending state")
|
|
var nested := {&"vip_priority": true, &"custom": {&"tags": [&"quiet"]}}
|
|
_expect(facilities.set_policy(&"inn", nested).is_success(), "unrelated generic policy keys retain compatibility")
|
|
nested[&"custom"][&"tags"].append(&"mutated")
|
|
_expect(facilities.get_instance(&"inn").policy_current[&"custom"][&"tags"] == [&"quiet"], "nested generic patches are copied")
|
|
_expect(facilities.set_policy(&"inn", {"price_mode": "affordable", "business_open": true}).is_success(), "string-key policy input is accepted")
|
|
_expect(facilities.get_instance(&"inn").policy_current[&"price_mode"] == &"affordable", "price IDs normalize consistently")
|
|
|
|
|
|
func _test_locked_quotes_and_scheduled_changes() -> void:
|
|
var facilities := _facilities()
|
|
var instance := facilities.get_instance(&"inn")
|
|
facilities.set_policy(&"inn", {&"price_mode": &"affordable"})
|
|
var queued := facilities.create_request(&"old", &"inn", [&"guest"], &"meal", 0)
|
|
_expect(queued.quoted_price == 20, "new request uses the facility's policy price")
|
|
_expect(facilities.enqueue_request(queued).is_success(), "quoted request enters queue")
|
|
var locked := queued.create_snapshot()
|
|
facilities.set_policy(&"inn", {&"price_mode": &"exorbitant"})
|
|
_expect(queued.create_snapshot() == locked, "queued price and policy remain locked across a price change")
|
|
var result := facilities.start_next_service(&"inn")
|
|
_expect(result.is_success(), "old quotation starts after a price change")
|
|
var active := facilities.get_service(StringName(result.details[&"service_id"]))
|
|
_expect(active.price_snapshot == 20 and active.policy_snapshot[&"price_mode"] == &"affordable", "active service uses its accepted price and policy")
|
|
var newest := facilities.create_request(&"new", &"inn", [&"other"], &"meal", 0)
|
|
_expect(newest.quoted_price == 63, "subsequent request sees the new price")
|
|
var explicit := facilities.create_request(&"explicit", &"inn", [&"another"], &"meal", 0, {&"quoted_price": 17})
|
|
_expect(explicit.quoted_price == 17, "an explicit quote is not multiplied a second time")
|
|
facilities.set_policy(&"inn", {&"price_mode": &"charitable"}, 5)
|
|
facilities.advance_one_minute(4)
|
|
_expect(instance.policy_current[&"price_mode"] == &"exorbitant", "scheduled price does not apply before its minute")
|
|
facilities.advance_one_minute(5)
|
|
_expect(instance.policy_current[&"price_mode"] == &"charitable", "scheduled price applies at its exact minute")
|
|
_expect(active.price_snapshot == 20 and active.policy_snapshot[&"price_mode"] == &"affordable", "completed service history preserves its accepted quotation")
|
|
_expect(instance.policy_pending.is_empty() and instance.policy_pending_revision == 0, "applied schedule clears its pending metadata")
|
|
|
|
|
|
func _test_closed_service_and_opening_hours() -> void:
|
|
var facilities := _facilities()
|
|
var instance := facilities.get_instance(&"inn")
|
|
var running := facilities.create_request(&"running", &"inn", [&"guest"], &"meal", 0)
|
|
facilities.enqueue_request(running)
|
|
var started := facilities.start_next_service(&"inn")
|
|
var active := facilities.get_service(StringName(started.details[&"service_id"]))
|
|
var waiting := facilities.create_request(&"waiting", &"inn", [&"waiter"], &"meal", 0)
|
|
facilities.enqueue_request(waiting)
|
|
_expect(facilities.set_policy(&"inn", {&"business_open": false}).is_success(), "operator can close a facility through policy")
|
|
_expect(not instance.operator_open and instance.state == Facility.State.CLOSED, "closure synchronizes operator flag and state immediately")
|
|
for minute: int in range(1, 5):
|
|
facilities.advance_one_minute(minute)
|
|
_expect(active.state == Service.State.COMPLETED, "an accepted service completes while closed")
|
|
_expect(facilities.get_queue(&"inn").size() == 1 and facilities.get_active_services(&"inn").is_empty(), "closure prevents new service starts")
|
|
facilities.set_policy(&"inn", {&"business_open": true}, 6)
|
|
facilities.advance_one_minute(5)
|
|
_expect(not instance.operator_open, "scheduled reopening does not occur early")
|
|
facilities.advance_one_minute(6)
|
|
_expect(instance.operator_open and facilities.get_active_services(&"inn").size() == 1, "scheduled reopening allows the next service")
|
|
facilities.set_opening_hours(&"inn", 600, 1_200, 6)
|
|
_expect(instance.state == Facility.State.CLOSED and instance.operator_open, "opening hours remain independent from operator intent")
|
|
facilities.set_policy(&"inn", {&"price_mode": &"expensive"})
|
|
_expect(instance.state == Facility.State.CLOSED, "price changes do not bypass opening hours")
|
|
facilities.advance_one_minute(600)
|
|
_expect(instance.state == Facility.State.OPEN, "scheduled opening retains business intent")
|
|
facilities.set_operational_status(&"inn", {&"operator_open": false}, 600)
|
|
_expect(not instance.operator_open and not instance.policy_current[&"business_open"], "generic operational closure stays synchronized with policy")
|
|
facilities.set_operational_status(&"inn", {&"operator_open": true}, 600)
|
|
_expect(instance.operator_open and instance.policy_current[&"business_open"], "generic operational reopening remains supported")
|
|
|
|
|
|
func _test_snapshot_restore_and_legacy_policy() -> void:
|
|
var facilities := _facilities()
|
|
facilities.set_policy(&"inn", {&"price_mode": &"expensive", &"business_open": true})
|
|
var accepted := facilities.create_request(&"accepted", &"inn", [&"guest"], &"meal", 0)
|
|
facilities.enqueue_request(accepted)
|
|
facilities.start_next_service(&"inn")
|
|
var waiting := facilities.create_request(&"waiting", &"inn", [&"waiter"], &"meal", 0)
|
|
facilities.enqueue_request(waiting)
|
|
facilities.set_policy(&"inn", {&"price_mode": &"charitable", &"business_open": false}, 8)
|
|
var snapshot := facilities.create_snapshot()
|
|
var restored := Facilities.new()
|
|
_expect(restored.restore_snapshot(snapshot), "current, pending, queued and active pricing survives restore")
|
|
_expect(restored.create_snapshot() == snapshot, "valid save restores byte-equivalent policies and quotations")
|
|
for minute: int in range(1, 9):
|
|
facilities.advance_one_minute(minute)
|
|
restored.advance_one_minute(minute)
|
|
_expect(restored.create_snapshot() == facilities.create_snapshot(), "restored pending policy replays deterministically")
|
|
var bad := snapshot.duplicate(true)
|
|
bad[&"instances"][0][&"policy_current"][&"price_mode"] = &"invalid"
|
|
var unchanged := restored.create_snapshot()
|
|
_expect(not restored.restore_snapshot(bad), "invalid saved price mode is rejected")
|
|
_expect(restored.create_snapshot() == unchanged, "bad price restore cannot change live state")
|
|
bad = snapshot.duplicate(true)
|
|
bad[&"instances"][0][&"policy_pending"][&"business_open"] = 0
|
|
_expect(not restored.restore_snapshot(bad), "invalid saved pending business state is rejected")
|
|
bad = snapshot.duplicate(true)
|
|
bad[&"requests"][0][&"policy_snapshot"][&"price_mode"] = &"invalid"
|
|
_expect(not restored.restore_snapshot(bad), "invalid saved request policy is rejected")
|
|
bad = snapshot.duplicate(true)
|
|
bad[&"instances"][0][&"operator_open"] = false
|
|
_expect(not restored.restore_snapshot(bad), "contradictory saved operator and business flags are rejected")
|
|
var legacy := _facilities()
|
|
legacy.set_operational_status(&"inn", {&"operator_open": false})
|
|
var legacy_snapshot := legacy.create_snapshot()
|
|
_expect(restored.restore_snapshot(legacy_snapshot), "legacy operator-only saves remain supported")
|
|
_expect(restored.create_snapshot() == legacy_snapshot and not restored.get_instance(&"inn").operator_open, "legacy closure restores without inserting policy keys")
|
|
var configured := Facility.new().configure(&"configured", &"cafeteria", {&"policy_current": {&"business_open": false}})
|
|
_expect(not configured.operator_open and configured.state == Facility.State.CLOSED, "configured business policy initializes operator state")
|
|
|
|
|
|
func _test_queue_release_is_atomic() -> void:
|
|
var guests := Guests.new()
|
|
guests.create_definition(&"visitor", {&"base_patience": 30})
|
|
var guest := guests.spawn_guest(&"waiting", &"visitor", 0, {&"state": Guest.State.DECIDING})
|
|
guests.enter_queue(&"waiting", &"inn", &"request", 0, 17)
|
|
guest.path_handle = {&"path": [Vector3i.ZERO]}
|
|
var callback_state := {&"calls": 0, &"allow": false, &"received": []}
|
|
var callback := func(guest_id: StringName, facility_id: StringName, request_id: StringName) -> bool:
|
|
callback_state[&"calls"] += 1
|
|
callback_state[&"received"] = [guest_id, facility_id, request_id]
|
|
return bool(callback_state[&"allow"])
|
|
var before := guest.create_snapshot()
|
|
_expect(not bool(guests.release_queue_for_replanning(&"waiting", 1, callback)[&"ok"]), "failed external queue removal rejects redecision")
|
|
_expect(guest.create_snapshot() == before, "failed external queue removal preserves guest state and patience")
|
|
callback_state[&"allow"] = true
|
|
guest.transition_tick = 1
|
|
guest.transitions_this_tick = Guests.MAX_TRANSITIONS_PER_TICK
|
|
before = guest.create_snapshot()
|
|
_expect(not bool(guests.release_queue_for_replanning(&"waiting", 1, callback)[&"ok"]), "full transition budget defers queue release")
|
|
_expect(callback_state[&"calls"] == 1 and guest.create_snapshot() == before, "transition preflight protects external ownership and guest state")
|
|
var reasons: Array[StringName] = []
|
|
guests.guest_state_changed.connect(func(_id: StringName, _old: int, _new: int, reason: StringName): reasons.append(reason))
|
|
_expect(bool(guests.release_queue_for_replanning(&"waiting", 2, callback)[&"ok"]), "queue release retries on the next available minute")
|
|
_expect(callback_state[&"received"] == [&"waiting", &"inn", &"request"], "external release receives the exact ownership tuple")
|
|
_expect(guest.state == Guest.State.DECIDING and guest.patience_remaining == 17, "closure causes redecision without a patience penalty")
|
|
_expect(guest.target_facility_id.is_empty() and guest.queue_request_id.is_empty() and guest.path_handle == null, "successful release clears obsolete target, request and path")
|
|
_expect(reasons == [&"FACILITY_CLOSED"], "closure publishes a distinct redecision reason")
|
|
_expect(not bool(guests.release_queue_for_replanning(&"waiting", 3, callback)[&"ok"]) and callback_state[&"calls"] == 2, "released guest cannot release the request twice")
|
|
_expect(not bool(guests.release_queue_for_replanning(&"missing", 3)[&"ok"]), "missing guests cannot enter the release path")
|
|
|
|
|
|
func _facilities() -> FacilityService:
|
|
var dining_cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(2, 0, 0), Vector3i(3, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 0), Vector3i(2, 1, 0), Vector3i(3, 1, 0)]
|
|
var kitchen_cells: Array[Vector3i] = [Vector3i(0, 0, 1), Vector3i(1, 0, 1), Vector3i(2, 0, 1), Vector3i(3, 0, 1), Vector3i(0, 1, 1), Vector3i(1, 1, 1), Vector3i(2, 1, 1), Vector3i(3, 1, 1)]
|
|
var facilities := Facilities.new()
|
|
facilities.register_instance(Facility.new().configure(&"inn", &"cafeteria", {
|
|
&"occupied_cells": dining_cells,
|
|
&"capacity": 1, &"queue_capacity": 8, &"base_price": 25,
|
|
&"base_quality": 50, &"base_service_minutes": 4,
|
|
}))
|
|
facilities.register_instance(Facility.new().configure(&"kitchen", &"kitchen", {&"origin": Vector3i(0, 0, WorldService.LAYER_BACK), &"occupied_cells": kitchen_cells}))
|
|
_expect(facilities.set_operational_status(&"inn", {&"related_facility_id": &"kitchen"}).is_success(), "isolated pricing Service uses explicit BACK kitchen; quote calculations unchanged")
|
|
return facilities
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
_checks += 1
|
|
if condition:
|
|
print(" ✓ %s" % message)
|
|
else:
|
|
_failures += 1
|
|
push_error(" ✗ %s" % message)
|