Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
This commit is contained in:
@@ -41,7 +41,8 @@ func _test_state_priority_and_opening_hours() -> void:
|
||||
&"degraded_reasons": [&"DIRTY"],
|
||||
}, 600)
|
||||
_expect(facility.state == FacilityInstanceType.State.BROKEN, "BROKEN wins over every other state")
|
||||
_expect(facility.state_reasons.size() == 6, "all simultaneous state reasons are retained")
|
||||
_expect(facility.state_reasons.size() == 7, "all simultaneous state reasons are retained")
|
||||
facility = _facility(&"undamaged_room", 1, 4, 1, 2)
|
||||
facility.set_operational_conditions({
|
||||
&"durability": 5_000,
|
||||
&"access_available": false,
|
||||
@@ -50,7 +51,7 @@ func _test_state_priority_and_opening_hours() -> void:
|
||||
&"active_staff_ids": [],
|
||||
&"degraded_reasons": [],
|
||||
}, 600)
|
||||
_expect(facility.state == FacilityInstanceType.State.NO_ACCESS, "NO_ACCESS wins after repair")
|
||||
_expect(facility.state == FacilityInstanceType.State.NO_ACCESS, "NO_ACCESS wins in an undamaged facility")
|
||||
facility.set_operational_conditions({&"access_available": true}, 600)
|
||||
_expect(facility.state == FacilityInstanceType.State.NO_POWER, "NO_POWER wins over staffing and closure")
|
||||
facility.set_operational_conditions({&"power_available": true}, 600)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
extends SceneTree
|
||||
|
||||
const Accrual = preload("res://scripts/economy/facility_upkeep_accrual.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func expect(value: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(label)
|
||||
|
||||
func _run() -> void:
|
||||
var service := Accrual.new()
|
||||
expect(not service.advance_to(1, {}).ok, "an explicit starting minute is required; no retroactive fees")
|
||||
expect(service.start_at(360).ok, "start at actual migration/start time")
|
||||
expect(not service.start_at(0).ok, "starting twice cannot wipe accumulated charges")
|
||||
service.advance_to(720, {&"room": 24, &"empty": 0})
|
||||
expect(service.get_report().pending_facility_units == {"room": 8640}, "six hours at 24/day accrues exactly six gold")
|
||||
service.advance_to(1080, {&"room": 24, &"new_room": 48})
|
||||
var checkpoint := service.create_snapshot()
|
||||
var restored := Accrual.new()
|
||||
expect(restored.restore_snapshot(checkpoint), "partial day can be restored")
|
||||
# The absent room has been demolished; its already accrued expense remains.
|
||||
var close := service.advance_to(1440, {&"new_room": 48})
|
||||
restored.advance_to(1440, {&"new_room": 48})
|
||||
expect(close.closed_periods.size() == 1, "00:00 closes exactly one period")
|
||||
var period: Dictionary = close.closed_periods[0]
|
||||
expect(period.period_start == 360 and period.period_end == 1440, "first partial day never bills time before initialization")
|
||||
expect(period.facility_units == {"new_room": 34560, "room": 17280}, "demolition stops future accrual but preserves incurred costs")
|
||||
expect(service.create_snapshot() == restored.create_snapshot(), "save/load preserves exact fractional accumulation")
|
||||
var before := service.create_snapshot()
|
||||
expect(service.advance_to(1440, {&"new_room": 48}).ok and service.create_snapshot() == before, "same-minute replay cannot charge twice")
|
||||
expect(not service.advance_to(1439, {}).ok and service.create_snapshot() == before, "rewind rejected without mutation")
|
||||
close.closed_periods[0].facility_units.room = 999
|
||||
expect(service.create_snapshot() == before, "returned period cannot mutate authoritative accrual")
|
||||
var report := service.get_report()
|
||||
report.closed_periods.clear()
|
||||
expect(service.create_snapshot() == before, "report is a detached copy")
|
||||
for rates: Dictionary in [{"bad": -1}, {"bad": 0.5}, {"bad": true}, {"": 1}, {"bad": Accrual.MAX_DAILY_RATE + 1}, {1: 5}]:
|
||||
expect(not service.advance_to(1441, rates).ok and service.create_snapshot() == before, "invalid rates reject atomically: %s" % str(rates))
|
||||
expect(not service.advance_to(2881, {}).ok and service.create_snapshot() == before, "unbounded batch rejected")
|
||||
# Integer numerator is retained; neither per-minute nor per-day truncation.
|
||||
var fractional := Accrual.new()
|
||||
fractional.start_at(1439)
|
||||
fractional.advance_to(1441, {"one_gold_per_day": 1})
|
||||
var fraction_report := fractional.get_report()
|
||||
expect(fraction_report.closed_periods[0].facility_units.one_gold_per_day == 1, "one minute charge is not rounded to zero")
|
||||
expect(fraction_report.pending_facility_units.one_gold_per_day == 1, "midnight split conserves the second minute")
|
||||
expect(fraction_report.gold_denominator == 1440, "settlement receives exact monetary unit denominator")
|
||||
var split := Accrual.new()
|
||||
var batched := Accrual.new()
|
||||
split.start_at(0)
|
||||
batched.start_at(0)
|
||||
for minute in range(1, 1441):
|
||||
split.advance_to(minute, {"b": 17, "a": 5})
|
||||
batched.advance_to(1440, {"a": 5, "b": 17})
|
||||
expect(split.create_snapshot() == batched.create_snapshot(), "input order and batching cannot change charges")
|
||||
var json_copy: Dictionary = JSON.parse_string(JSON.stringify(split.create_snapshot()))
|
||||
# JSON.parse_string produces floats; the project's snapshot codec handles
|
||||
# integer preservation. Do not accept lossy/unvalidated raw JSON as a save.
|
||||
expect(not restored.restore_snapshot(json_copy), "raw floating JSON fields are not silently accepted as integers")
|
||||
var valid := split.create_snapshot()
|
||||
var fresh := Accrual.new()
|
||||
expect(fresh.restore_snapshot(valid), "closed periods restore")
|
||||
for change: Dictionary in [
|
||||
{"version": 2}, {"current_minute": 1439}, {"started_at": -2},
|
||||
{"period_start": 1441}, {"extra": 1}, {"pending_units": {"room": 1}},
|
||||
{"closed_periods": []}, {"current_minute": true},
|
||||
]:
|
||||
var invalid := valid.duplicate(true)
|
||||
invalid.merge(change, true)
|
||||
expect(not fresh.restore_snapshot(invalid) and fresh.create_snapshot() == valid, "invalid save rejected atomically: %s" % str(change))
|
||||
var invalid := valid.duplicate(true)
|
||||
invalid.closed_periods.append(invalid.closed_periods[0].duplicate(true))
|
||||
expect(not fresh.restore_snapshot(invalid) and fresh.create_snapshot() == valid, "duplicated financial period rejected")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.closed_periods[0].facility_units.a = Accrual.MAX_DAILY_RATE * 1440 + 1
|
||||
expect(not fresh.restore_snapshot(invalid), "impossible stored amount rejected")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.closed_periods[0].period_start = 1
|
||||
expect(not fresh.restore_snapshot(invalid), "period gap rejected")
|
||||
var empty := Accrual.new()
|
||||
expect(fresh.restore_snapshot(empty.create_snapshot()), "unstarted state can restore")
|
||||
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bnxcvawdha8tw
|
||||
@@ -0,0 +1,128 @@
|
||||
extends SceneTree
|
||||
|
||||
const Accrual = preload("res://scripts/economy/facility_upkeep_accrual.gd")
|
||||
const Settlement = preload("res://scripts/economy/facility_upkeep_settlement.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
class RejectingEconomy:
|
||||
extends EconomyService
|
||||
func post(_entries: Array, _key: StringName = &"", _transaction: StringName = &"", _notify := true) -> CommandResult:
|
||||
return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "injected refusal")
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func expect(value: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(label)
|
||||
|
||||
func _economy(cash: int) -> EconomyService:
|
||||
var economy := EconomyService.new({&"PLAYER_CASH": false})
|
||||
if cash > 0:
|
||||
economy.initialize_account(&"PLAYER_CASH", cash)
|
||||
expect(Settlement.prepare_accounts(economy), "prepare designated expense/payable accounts")
|
||||
return economy
|
||||
|
||||
func _period(start: int, units: Dictionary) -> Dictionary:
|
||||
return {&"period_start": start, &"period_end": (start / 1440 + 1) * 1440, &"facility_units": units}
|
||||
|
||||
func _run() -> void:
|
||||
var economy := _economy(5)
|
||||
var service := Settlement.new()
|
||||
var period := _period(0, {"a": 10 * 1440, "b": 2 * 1440 + 10})
|
||||
var first := service.settle(period, economy)
|
||||
expect(first.ok and not first.replayed, "first midnight settles")
|
||||
expect(economy.get_balance(Settlement.CASH) == 0, "cash cannot become negative")
|
||||
expect(economy.get_balance(Settlement.EXPENSE) == 12, "expense includes unpaid amount")
|
||||
expect(economy.get_balance(Settlement.PAYABLE) == -7, "seven gold remains payable")
|
||||
expect(service.get_report().arrears_by_facility == {"a": 5, "b": 2}, "insufficient funds allocate deterministically by facility ID")
|
||||
expect(service.get_report().fraction_units == {"b": 10}, "sub-gold expense is retained")
|
||||
expect(economy.has_pending_transaction_notification(first.transaction_id), "notification is deferred to outer transaction commit")
|
||||
expect(economy.commit_transaction_notification(first.transaction_id), "notification can be published after state commit")
|
||||
var guard := economy.create_snapshot()
|
||||
var snapshot := service.create_snapshot()
|
||||
var replay := service.settle(period, economy)
|
||||
expect(replay.ok and replay.replayed and replay.transaction_id == &"", "same settlement is idempotent and emits no notification")
|
||||
expect(economy.create_snapshot() == guard and service.create_snapshot() == snapshot, "replay changes neither balances nor receipts")
|
||||
var wrong := period.duplicate(true)
|
||||
wrong.facility_units.a += 1
|
||||
expect(not service.settle(wrong, economy).ok and economy.create_snapshot() == guard, "changed request cannot reuse a closed period")
|
||||
expect(not service.settle(_period(2880, {}), economy).ok, "missing financial day rejected")
|
||||
# Facility a has been demolished; its debt must still be paid next midnight.
|
||||
economy.initialize_account(Settlement.CASH, 20, 1500, &"cash.for.next.day")
|
||||
var second := service.settle(_period(1440, {"b": 1430}), economy)
|
||||
expect(second.ok, "following day settles including prior arrears")
|
||||
expect(service.get_arrears_through(1439) == 0, "no historical debt before first settlement")
|
||||
expect(service.get_arrears_through(1440) == 7, "historical close retains its original debt after repayment")
|
||||
expect(service.get_arrears_through(2879) == 7, "interim query uses the last completed settlement")
|
||||
expect(service.get_arrears_through(2880) == 0, "repayment boundary clears current debt")
|
||||
expect(service.get_arrears_through(9999) == 0, "future query does not invent debt")
|
||||
expect(economy.get_balance(Settlement.EXPENSE) == 13, "prior debt repayment is not a new expense")
|
||||
expect(economy.get_balance(Settlement.CASH) == 12 and economy.get_balance(Settlement.PAYABLE) == 0, "eight gold pays debt and new fractional carry")
|
||||
expect(service.get_report().arrears_total == 0 and service.get_report().fraction_units.is_empty(), "debt and fractions cleared after payment")
|
||||
expect(service.validate_ledger(economy) and economy.validate_ledger().ok, "service and double-entry ledger reconcile")
|
||||
var expense_entries := 0
|
||||
var payment_entries := 0
|
||||
for entry: LedgerEntry in economy.get_entries():
|
||||
if entry.category == &"MAINTENANCE_ACCRUAL":
|
||||
expense_entries += 1
|
||||
expect(entry.absolute_minute in [1439, 2879], "expense belongs to the day being closed")
|
||||
if entry.category == &"MAINTENANCE_PAYMENT":
|
||||
payment_entries += 1
|
||||
expect(entry.absolute_minute in [1440, 2880], "cash payment records actual midnight")
|
||||
expect(expense_entries == 3 and payment_entries == 3, "only positive recognized expenses and payments produce entries")
|
||||
var loaded_economy := EconomyService.new()
|
||||
expect(loaded_economy.restore_snapshot(economy.create_snapshot()), "ledger restores")
|
||||
var loaded := Settlement.new()
|
||||
expect(loaded.restore_snapshot(service.create_snapshot(), loaded_economy), "receipts restore with ledger crosschecks")
|
||||
expect(loaded.get_report() == service.get_report(), "derived debt/fractions rebuild exactly")
|
||||
var future := _period(2880, {"new": 3 * 1440})
|
||||
expect(service.settle(future, economy).ok and loaded.settle(future, loaded_economy).ok, "both timelines advance")
|
||||
expect(service.create_snapshot() == loaded.create_snapshot() and economy.create_snapshot() == loaded_economy.create_snapshot(), "same future after reload")
|
||||
var valid := loaded.create_snapshot()
|
||||
var invalid := valid.duplicate(true)
|
||||
invalid.receipts[0].rows[0].new_expense += 1
|
||||
expect(not loaded.restore_snapshot(invalid, loaded_economy) and loaded.create_snapshot() == valid, "forged expense rejected atomically")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.receipts[0].rows[0].paid = true
|
||||
expect(not loaded.restore_snapshot(invalid, loaded_economy), "boolean amount rejected")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.receipts[0].transaction_id = "missing"
|
||||
expect(not loaded.restore_snapshot(invalid, loaded_economy), "missing ledger transaction rejected")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.receipts[0].period.facility_units.a += 1
|
||||
expect(not loaded.restore_snapshot(invalid, loaded_economy), "forged numerator and unchanged receipt rejected")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.receipts.pop_front()
|
||||
expect(not loaded.restore_snapshot(invalid, loaded_economy), "omitting settled debt history rejected by ledger ownership")
|
||||
invalid = valid.duplicate(true)
|
||||
invalid.version = 2
|
||||
expect(not loaded.restore_snapshot(invalid, loaded_economy), "unknown save version rejected")
|
||||
var unrelated := _economy(100)
|
||||
expect(not loaded.restore_snapshot(valid, unrelated), "receipts cannot be restored onto unrelated money")
|
||||
var rejecting := RejectingEconomy.new({Settlement.CASH: false})
|
||||
expect(Settlement.prepare_accounts(rejecting), "prepare rejecting ledger")
|
||||
var failed_service := Settlement.new()
|
||||
var before_service := failed_service.create_snapshot()
|
||||
var before_ledger := rejecting.create_snapshot()
|
||||
expect(not failed_service.settle(_period(0, {"room": 1440}), rejecting).ok, "ledger failure propagates")
|
||||
expect(failed_service.create_snapshot() == before_service and rejecting.create_snapshot() == before_ledger, "failed ledger post leaves all authoritative state unchanged")
|
||||
var empty := Settlement.new()
|
||||
var no_cash := _economy(0)
|
||||
expect(empty.settle(_period(0, {}), no_cash).ok, "empty day requires no zero-value ledger entry")
|
||||
var empty_loaded := Settlement.new()
|
||||
expect(empty_loaded.restore_snapshot(empty.create_snapshot(), no_cash), "empty settled day persists")
|
||||
# End-to-end cost math at completion/demolition boundaries with real ledger.
|
||||
var accrual := Accrual.new()
|
||||
accrual.start_at(360)
|
||||
accrual.advance_to(720, {})
|
||||
accrual.advance_to(1080, {"built_at_noon": 24})
|
||||
var closed: Dictionary = accrual.advance_to(1440, {}).closed_periods[0]
|
||||
var life := Settlement.new()
|
||||
var life_cash := _economy(100)
|
||||
expect(life.settle(closed, life_cash).ok and life_cash.get_balance(Settlement.EXPENSE) == 6, "six-hour completed ownership charges six gold after demolition")
|
||||
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bjqlm2h0tv05v
|
||||
@@ -123,7 +123,6 @@ func _test_interruptions() -> void:
|
||||
{&"off": {&"power_available": false}, &"on": {&"power_available": true}, &"state": &"NO_POWER"},
|
||||
{&"off": {&"access_available": false}, &"on": {&"access_available": true}, &"state": &"NO_ACCESS"},
|
||||
{&"off": {&"operator_open": false}, &"on": {&"operator_open": true}, &"state": &"CLOSED"},
|
||||
{&"off": {&"durability": 0}, &"on": {&"durability": 10000}, &"state": &"BROKEN"},
|
||||
]
|
||||
for item: Dictionary in cases:
|
||||
facilities.set_operational_status(&"kitchen.one", item[&"off"], kitchen.current_minute)
|
||||
@@ -138,6 +137,11 @@ func _test_interruptions() -> void:
|
||||
kitchen.set_plan(&"kitchen.one", true, 4)
|
||||
_tick(kitchen, 10)
|
||||
_expect(inventory.total_quantity(&"kitchen.stock", Catalog.BASIC_MEAL) == 4, "resuming earns only missing operating minutes")
|
||||
facilities.set_operational_status(&"kitchen.one", {&"durability": 0}, kitchen.current_minute)
|
||||
_expect(kitchen.get_report(&"kitchen.one")[&"state"] == &"BROKEN", "destroyed kitchen stops production")
|
||||
facilities.set_operational_status(&"kitchen.one", {&"durability": 10000}, kitchen.current_minute)
|
||||
_expect(kitchen.get_report(&"kitchen.one")[&"state"] == &"SUSPENDED", "repair does not invent suspension recovery")
|
||||
_expect(not facilities.get_instance(&"kitchen.one").allows_logistics(), "suspended kitchen cannot supply deliveries")
|
||||
|
||||
|
||||
func _test_expiry_and_retry() -> void:
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
extends SceneTree
|
||||
const PowerProjector = preload("res://scripts/mana/mana_power_projection.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
func expect(ok: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
func _initialize() -> void:
|
||||
var facilities := FacilityService.new()
|
||||
var network := ManaNetworkService.new()
|
||||
for id: StringName in [&"zero_cost", &"powered", &"damaged", &"closed"]:
|
||||
facilities.create_instance({&"instance_id": id, &"definition_id": &"standard_room", &"staff_min": 0})
|
||||
facilities.set_operational_status(&"damaged", {&"suspension_reason": &"RAIDED"}, 0)
|
||||
facilities.set_operational_status(&"closed", {&"operator_open": false}, 0)
|
||||
network.register_source(&"source", 8)
|
||||
network.register_consumer(&"powered", 10)
|
||||
network.connect_nodes(&"source", &"powered")
|
||||
network.evaluate_now(0)
|
||||
var result := PowerProjector.apply(facilities, network, 10, 0)
|
||||
expect(result[&"ok"] and facilities.get_instance(&"powered").degraded_reasons.has(&"MANA_BROWNOUT"), "partial network supply retains brownout reason")
|
||||
result = PowerProjector.apply(facilities, network, 0, 1)
|
||||
expect(result[&"ok"] and result[&"changed_facilities"].size() == 4, "zero reserve stops every facility including zero-demand facilities")
|
||||
for facility: FacilityInstance in facilities.get_instances():
|
||||
expect(not facility.power_available and facility.operation_state != FacilityInstance.OperationState.OPERATING, "no facility operates with zero reserve: " + String(facility.instance_id))
|
||||
expect(PowerProjector.apply(facilities, network, 0, 1)[&"changed_facilities"].is_empty(), "unchanged projection emits no repeated status changes")
|
||||
var checkpoint := facilities.create_snapshot()
|
||||
var restored := FacilityService.new()
|
||||
expect(restored.restore_snapshot(checkpoint), "power and suspension conditions survive service checkpoint")
|
||||
PowerProjector.apply(restored, network, 4, 2)
|
||||
expect(restored.get_instance(&"zero_cost").power_available, "zero-demand facility recovers supply")
|
||||
expect(restored.get_instance(&"damaged").suspension_reason == &"RAIDED" and restored.get_instance(&"damaged").operation_state == FacilityInstance.OperationState.SUSPENDED, "reserve recovery never releases raid suspension")
|
||||
expect(not restored.get_instance(&"closed").operator_open, "recovery never reopens player-closed facility")
|
||||
expect(restored.get_instance(&"powered").degraded_reasons.has(&"MANA_BROWNOUT"), "recovery retains actual network brownout")
|
||||
network.set_source_output(&"source", 0)
|
||||
network.evaluate_now(3)
|
||||
PowerProjector.apply(restored, network, 4, 3)
|
||||
expect(not restored.get_instance(&"powered").power_available, "positive reserve does not override absent network supply")
|
||||
var before := restored.create_snapshot()
|
||||
expect(not PowerProjector.apply(restored, network, -1, 4)[&"ok"] and restored.create_snapshot() == before, "invalid reserve rejected without changes")
|
||||
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://crrblhmc2umm8
|
||||
@@ -0,0 +1,56 @@
|
||||
extends SceneTree
|
||||
const Reserve = preload("res://scripts/mana/mana_reserve_balance.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
func expect(ok: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
func _initialize() -> void:
|
||||
var reserve = Reserve.new()
|
||||
expect(not reserve.advance_to(1, 0, 0)[&"ok"], "uninitialized updates rejected")
|
||||
expect(not reserve.initialize(3, 2, 0)[&"ok"], "invalid initial capacity rejected")
|
||||
expect(reserve.initialize(7, 10, 360)[&"ok"], "caller supplies all initial values")
|
||||
var flow: Dictionary = reserve.advance_to(370, 5, 8)[&"flow"]
|
||||
expect(flow[&"consumed"] == 8 and flow[&"after"] == 4 and flow[&"shortage"] == 0, "generation funds demand before reserve changes")
|
||||
var saved: Dictionary = reserve.create_snapshot()
|
||||
expect(reserve.advance_to(370, 5, 8)[&"replayed"] and reserve.create_snapshot() == saved, "same boundary cannot consume twice")
|
||||
expect(not reserve.advance_to(370, 5, 9)[&"ok"] and reserve.create_snapshot() == saved, "conflicting boundary replay rejected atomically")
|
||||
flow = reserve.advance_to(380, 0, 6)[&"flow"]
|
||||
expect(flow[&"consumed"] == 4 and flow[&"shortage"] == 2 and reserve.is_depleted(), "short supply depletes exactly without negative reserve")
|
||||
var depleted: Dictionary = reserve.create_snapshot()
|
||||
var resumed = Reserve.new()
|
||||
expect(resumed.restore_snapshot(depleted) and resumed.is_depleted(), "depletion survives save restore")
|
||||
flow = resumed.advance_to(390, 3, 0)[&"flow"]
|
||||
expect(flow[&"after"] == 3 and not resumed.is_depleted(), "supply replenishes stopped reserve")
|
||||
flow = resumed.advance_to(400, 10, 0)[&"flow"]
|
||||
expect(flow[&"after"] == 10 and flow[&"overflow"] == 3, "overflow explicitly accounts for capacity cap")
|
||||
flow = resumed.advance_to(410, 9, 12)[&"flow"]
|
||||
expect(flow[&"after"] == 7 and flow[&"overflow"] == 0 and flow[&"consumed"] == 12, "full storage does not discard supply needed by same-interval demand")
|
||||
for bad: Variant in [-1, 1.5, "3", true, Reserve.LIMIT + 1]:
|
||||
saved = resumed.create_snapshot()
|
||||
expect(not resumed.advance_to(420, bad, 1)[&"ok"] and resumed.create_snapshot() == saved, "invalid supply rejected without state change: %s" % bad)
|
||||
var canonical: Dictionary = resumed.create_snapshot()
|
||||
for field: String in ["amount", "capacity", "minute", "version"]:
|
||||
var corrupt := canonical.duplicate(true)
|
||||
corrupt[field] = -1
|
||||
expect(not resumed.restore_snapshot(corrupt) and resumed.create_snapshot() == canonical, "corrupt %s rejected atomically" % field)
|
||||
for field: String in ["consumed", "shortage", "overflow", "after", "before", "to_minute"]:
|
||||
var corrupt := canonical.duplicate(true)
|
||||
corrupt[&"last_flow"][field] += 1
|
||||
expect(not resumed.restore_snapshot(corrupt) and resumed.create_snapshot() == canonical, "flow conservation tampering rejected: " + field)
|
||||
var extra := canonical.duplicate(true)
|
||||
extra[&"unknown"] = 1
|
||||
expect(not resumed.restore_snapshot(extra), "unknown schema field rejected")
|
||||
var restored = Reserve.new()
|
||||
expect(restored.restore_snapshot(canonical), "valid checkpoint restores")
|
||||
expect(restored.advance_to(420, 2, 9) == resumed.advance_to(420, 2, 9), "restored next boundary reproduces same flow")
|
||||
expect(restored.create_snapshot() == resumed.create_snapshot() and restored.is_depleted(), "exact zero depletion and replay state agree")
|
||||
var limit = Reserve.new()
|
||||
expect(limit.initialize(Reserve.LIMIT, Reserve.LIMIT, 0)[&"ok"], "upper bound initialization")
|
||||
flow = limit.advance_to(1, Reserve.LIMIT, 0)[&"flow"]
|
||||
expect(flow[&"overflow"] == Reserve.LIMIT and limit.get_amount() == Reserve.LIMIT, "large values conserve without wrap")
|
||||
expect(restored.restore_snapshot(limit.create_snapshot()), "large flow snapshot accepted")
|
||||
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://djywnbowl8qkj
|
||||
@@ -0,0 +1,72 @@
|
||||
extends SceneTree
|
||||
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func expect(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
|
||||
func _run() -> void:
|
||||
var service := FacilityService.new()
|
||||
for id: StringName in [&"a", &"b", &"c"]:
|
||||
expect(service.create_instance({&"instance_id": id, &"definition_id": &"test", &"capacity": 100, &"customer_capacity": 3, &"queue_capacity": 10, &"base_service_minutes": 10}).is_success(), "register")
|
||||
var a := service.get_instance(&"a")
|
||||
expect(service.set_operational_status(&"a", {&"related_facility_id": &"b"}).is_success(), "set dependency")
|
||||
expect(service.set_operational_status(&"b", {&"related_facility_id": &"c"}).is_success(), "set nested dependency")
|
||||
expect(not service.set_operational_status(&"c", {&"related_facility_id": &"a"}).is_success(), "reject cycle")
|
||||
service.set_operational_status(&"c", {&"power_available": false})
|
||||
expect(a.operation_state == FacilityInstance.OperationState.INACTIVE and not a.related_facility_operating, "nested dependency checks actual operation")
|
||||
service.set_operational_status(&"c", {&"power_available": true})
|
||||
expect(a.operation_state == FacilityInstance.OperationState.OPERATING, "chain reopens deterministically")
|
||||
var first := service.create_request(&"first", &"a", [&"g1", &"g2"], &"test", 0)
|
||||
expect(service.enqueue_request(first).is_success(), "group enqueues")
|
||||
expect(service.start_next_service(&"a").is_success(), "first group starts")
|
||||
expect(a.customer_count() == 2 and a.available_slots() == 1, "count customers, not service instances or storage capacity")
|
||||
var second := service.create_request(&"second", &"a", [&"g3", &"g4"], &"test", 0)
|
||||
service.enqueue_request(second)
|
||||
expect(not service.start_next_service(&"a").is_success(), "group cannot exceed simultaneous customer capacity")
|
||||
expect(a.customer_count() == 2 and a.queue_entries.size() == 1, "waiting customers excluded")
|
||||
service.set_operational_status(&"c", {&"operator_open": false})
|
||||
service.advance_one_minute(1)
|
||||
var active: ServiceInstance = a.active_services.values()[0]
|
||||
expect(active.elapsed_minutes == 0, "dependent service pauses without progress")
|
||||
service.set_operational_status(&"c", {&"operator_open": true})
|
||||
service.advance_one_minute(2)
|
||||
expect(active.elapsed_minutes == 1, "service resumes after dependency operates")
|
||||
service.set_operational_status(&"a", {&"durability": 0})
|
||||
service.set_operational_status(&"a", {&"durability": 10000, &"operator_open": true})
|
||||
expect(a.operation_state == FacilityInstance.OperationState.SUSPENDED, "repair or opening does not release destruction suspension")
|
||||
expect(not service.set_operational_status(&"a", {&"suspension_reason": &""}).is_success(), "reject undefined recovery")
|
||||
service.advance_one_minute(3)
|
||||
expect(a.active_services.is_empty(), "suspended facility terminates service")
|
||||
var before_invalid := service.create_snapshot()
|
||||
expect(not service.set_operational_status(&"c", {&"related_facility_id": &"missing"}, 1000).is_success() and service.create_snapshot() == before_invalid, "invalid dependency cannot advance facility clock")
|
||||
service.set_opening_hours(&"c", 60, 120)
|
||||
expect(not service.get_instance(&"b").related_facility_operating, "opening schedule immediately updates dependents")
|
||||
var snapshot := service.create_snapshot()
|
||||
var restored := FacilityService.new()
|
||||
expect(restored.restore_snapshot(snapshot), "roundtrip")
|
||||
expect(restored.create_snapshot() == snapshot, "saved data preserved")
|
||||
expect(restored.get_instance(&"a").operation_state == FacilityInstance.OperationState.SUSPENDED, "suspension survives load")
|
||||
var bad := snapshot.duplicate(true)
|
||||
bad[&"instances"][0][&"customer_capacity"] = "3"
|
||||
expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == snapshot, "invalid capacity rejected atomically")
|
||||
bad = snapshot.duplicate(true)
|
||||
bad[&"instances"][2][&"related_facility_id"] = "a"
|
||||
expect(not restored.restore_snapshot(bad), "saved cycle rejected")
|
||||
var legacy := FacilityInstance.new().configure(&"legacy", &"warehouse", {&"capacity": 100})
|
||||
expect(legacy.customer_capacity == -1, "generic capacity never defines customer maximum")
|
||||
var quote := FacilityPricing.quote_service(100, {}, &"appetite", [&"appetite"], 25)
|
||||
expect(quote[&"total"] == 125 and quote[&"resolved"], "matching deficit adds specified surcharge")
|
||||
quote = FacilityPricing.quote_service(100, {}, &"appetite", [&"relaxation"], 25)
|
||||
expect(quote[&"total"] == 100 and quote[&"eligible"], "mismatch remains usable without surcharge")
|
||||
quote = FacilityPricing.quote_service(100, {}, &"appetite", [&"appetite"])
|
||||
expect(not quote[&"resolved"], "unspecified surcharge does not invent a rate")
|
||||
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://clr3yamqu0mbk
|
||||
@@ -173,6 +173,20 @@ func _run_service_sale_with_round_trip() -> void:
|
||||
_expect(active[0].price_snapshot == 25 and active[0].duration_minutes == 3, "service freezes quoted price and duration")
|
||||
|
||||
var mid_snapshot: Dictionary = _runtime.create_snapshot()
|
||||
var before_elapsed: int = active[0].elapsed_minutes
|
||||
var before_wallet: int = _runtime.economy_service.get_balance(&"GUEST_WALLET:guest_1")
|
||||
_state.set_resource(&"mana", 0)
|
||||
_clock.advance_minutes(5)
|
||||
var paused_service: ServiceInstance = _runtime.facility_service.get_service(service_id)
|
||||
_expect(paused_service.state == ServiceInstance.State.PAUSED and paused_service.elapsed_minutes == before_elapsed, "reserve depletion pauses active service without advancing work")
|
||||
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_1") == before_wallet, "paused service does not charge guest")
|
||||
_expect(_runtime.inventory_service.total_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 2, "paused service retains reserved ingredients")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "paused service and empty reserve restore together")
|
||||
_state.set_resource(&"mana", 100)
|
||||
# A just-built consumer may still await its first network allocation.
|
||||
_clock.advance_minutes(10 - (_clock.absolute_minute % 10) + 3)
|
||||
_expect(_runtime.facility_service.get_service(service_id).state == ServiceInstance.State.COMPLETED and _runtime.economy_service.get_balance(&"GUEST_WALLET:guest_1") == before_wallet - 25, "reserve recovery completes and charges once")
|
||||
_expect(_runtime.restore_snapshot(mid_snapshot), "rewind service outage drill before ordinary replay")
|
||||
var mid_digest := SimulationDigest.compute(mid_snapshot)
|
||||
_clock.advance_minutes(3)
|
||||
var completed: ServiceInstance = _runtime.facility_service.get_service(service_id)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
extends SceneTree
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
func expect(ok: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not ok: failures += 1; push_error(label)
|
||||
func _initialize() -> void: call_deferred("run")
|
||||
func fixture(capacity: int, customer_capacity: int) -> FacilityService:
|
||||
var service := FacilityService.new()
|
||||
expect(service.create_instance({&"instance_id": &"room", &"definition_id": &"test", &"capacity": capacity, &"customer_capacity": customer_capacity, &"queue_capacity": 10}).is_success(), "create independent service/customer limits")
|
||||
return service
|
||||
func enqueue(service: FacilityService, id: StringName, guests: Array[StringName]) -> void:
|
||||
expect(service.enqueue_request(service.create_request(id,&"room",guests,&"test",0)).is_success(), "queue " + String(id))
|
||||
func run() -> void:
|
||||
for capacity: int in [2,100]:
|
||||
var service := fixture(capacity,3)
|
||||
enqueue(service,&"first",[&"a",&"b"])
|
||||
enqueue(service,&"second",[&"c"])
|
||||
enqueue(service,&"overflow",[&"d"])
|
||||
# Explicit priority keeps the intended arrival ordering independent of IDs.
|
||||
service.get_request(&"first").priority=3
|
||||
service.get_request(&"second").priority=2
|
||||
var first := service.start_next_service(&"room",0)
|
||||
expect(first.is_success() and first.details.get(&"slot",-1)==0,"first group uses service slot zero regardless of headcount limit")
|
||||
var saved := service.create_snapshot()
|
||||
var second := service.start_next_service(&"room",0)
|
||||
expect(second.is_success() and second.details.get(&"slot",-1)==1,"second group uses service slot one")
|
||||
expect(service.get_instance(&"room").customer_count()==3,"two services consume three customer places")
|
||||
expect(not service.start_next_service(&"room",0).is_success(),"fourth customer still rejected")
|
||||
expect(service.restore_snapshot(saved),"restore first group checkpoint")
|
||||
var replay := service.start_next_service(&"room",0)
|
||||
expect(replay.is_success() and replay.details==second.details,"restored start returns same service and slot")
|
||||
var nested := fixture(2,3)
|
||||
enqueue(nested,&"a",[&"a",&"b"]);enqueue(nested,&"b",[&"c"])
|
||||
var callback_results: Array[CommandResult] = []
|
||||
var on_started := func(started: ServiceInstance) -> void:
|
||||
if started.request_id==&"a": callback_results.append(nested.start_next_service(&"room",0))
|
||||
nested.service_started.connect(on_started)
|
||||
var outer := nested.start_next_service(&"room",0)
|
||||
expect(outer.is_success() and outer.details.get(&"slot",-1)==0,"start result is fixed before notification callbacks")
|
||||
expect(callback_results.size()==1 and callback_results[0].is_success() and callback_results[0].details.get(&"slot",-1)==1,"nested start has its own slot result")
|
||||
nested.service_started.disconnect(on_started)
|
||||
print("Service start slots: %d checks, %d failures" % [checks,failures]);quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cwshoytei8ktv
|
||||
Reference in New Issue
Block a user