366 lines
32 KiB
GDScript
366 lines
32 KiB
GDScript
extends SceneTree
|
|
|
|
## Real command/ledger boundaries; failure injections are disclosed separately.
|
|
class RejectingPaymentEconomy:
|
|
extends EconomyService
|
|
func transfer(_from: StringName, _to: StringName, _amount: int,
|
|
_minute: int = -1, _category: StringName = DEFAULT_CATEGORY,
|
|
_source: StringName = &"", _key: StringName = &"", _memo: StringName = &"",
|
|
_related: StringName = &"", _notify := true) -> CommandResult:
|
|
return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "Payment refusal fixture")
|
|
|
|
class RejectingNotificationEconomy:
|
|
extends EconomyService
|
|
func commit_transaction_notification(_transaction: StringName) -> bool:
|
|
return false
|
|
|
|
class ProbedInventory:
|
|
extends InventoryService
|
|
var transaction_boundary: Dictionary = {}
|
|
func add_stack(owner: StringName, item: StringName, quantity: int,
|
|
quality: int = DEFAULT_QUALITY, expiry: int = -1, created: int = -1,
|
|
weight: int = DEFAULT_UNIT_WEIGHT, stack_id: StringName = &"") -> Dictionary:
|
|
transaction_boundary = create_snapshot()
|
|
return super.add_stack(owner, item, quantity, quality, expiry, created, weight, stack_id)
|
|
|
|
class PartialFailInventory:
|
|
extends ProbedInventory
|
|
func add_stack(owner: StringName, item: StringName, quantity: int,
|
|
quality: int = DEFAULT_QUALITY, expiry: int = -1, created: int = -1,
|
|
weight: int = DEFAULT_UNIT_WEIGHT, stack_id: StringName = &"") -> Dictionary:
|
|
super.add_stack(owner, item, quantity, quality, expiry, created, weight, stack_id)
|
|
return {&"ok": false, &"code": &"INJECTED_AFTER_STOCK"}
|
|
|
|
var _runtime: Variant
|
|
var _clock: Variant
|
|
var _state: Variant
|
|
var _warehouse: StringName
|
|
var _built: Dictionary
|
|
var _checks := 0
|
|
var _failures := 0
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
func _run() -> void:
|
|
_runtime = root.get_node("SimulationRuntime")
|
|
_clock = root.get_node("GameClock")
|
|
_state = root.get_node("GameState")
|
|
_clock.set_process(false)
|
|
_clock.reset(1, 6, 0)
|
|
_state.reset()
|
|
_expect(_runtime.initialize(_clock, _state, 420_501)[&"valid"], "canonical Runtime initializes")
|
|
_clock.set_paused(true)
|
|
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
_expect(_runtime.submit_command(&"build_facility", {&"definition_id": &"warehouse", &"origin": Vector3i(-8, 0, 1)}).is_success(), "ordinary paid warehouse build")
|
|
_clock.advance_minutes(180)
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"warehouse": _warehouse = facility.instance_id
|
|
_expect(not _warehouse.is_empty(), "ordinary construction yields a warehouse")
|
|
if _warehouse.is_empty(): _finish(); return
|
|
_built = _runtime.create_snapshot()
|
|
_test_receipt_codec()
|
|
_test_default_manual_and_paid_order()
|
|
_test_invalid_input_and_corrupt_save()
|
|
_test_atomic_failures_and_same_minute_retry()
|
|
_test_hold_disable_and_removal()
|
|
_test_actual_budget_power_reservation_and_terminal()
|
|
_test_multiple_warehouse_order()
|
|
_test_legacy13_and_composed_rollback()
|
|
_finish()
|
|
|
|
func _plan(enabled: bool, target: int, key: StringName = &"") -> CommandResult:
|
|
return _runtime.submit_command(&"set_supply_auto_purchase", {&"warehouse_id": _warehouse,
|
|
&"item_id": SupplyCatalog.RAW_FOOD, &"enabled": enabled, &"target_stock": target}, &"player", _warehouse, 75, -1, key)
|
|
|
|
func _stock() -> int:
|
|
var facility: FacilityInstance = _runtime.facility_service.get_instance(_warehouse)
|
|
return _runtime.inventory_service.total_quantity(facility.local_inventory_id, SupplyCatalog.RAW_FOOD)
|
|
|
|
func _test_receipt_codec() -> void:
|
|
var memo := SupplyAutoPurchasePlans.receipt_memo(SupplyCatalog.RAW_FOOD, 6, 3, 18)
|
|
var decoded := SupplyAutoPurchasePlans.decode_receipt_memo(memo)
|
|
_expect(decoded == {&"item_id": String(SupplyCatalog.RAW_FOOD), &"quantity": 6, &"unit_price": 3, &"total_cost": 18}, "canonical versioned actual quote codec is lossless")
|
|
decoded[&"quantity"] = 3
|
|
_expect(SupplyAutoPurchasePlans.decode_receipt_memo(memo)[&"quantity"] == 6, "decoded receipt is independently owned")
|
|
var punctuation: StringName = &"item:part.日本語"
|
|
_expect(SupplyAutoPurchasePlans.decode_receipt_memo(SupplyAutoPurchasePlans.receipt_memo(punctuation, 6, 3, 18))[&"item_id"] == String(punctuation), "length encoding handles punctuation and Unicode without ambiguity")
|
|
var prefix: String = SupplyAutoPurchasePlans.RECEIPT_PREFIX + "19:ingredient/raw_food:"
|
|
for malformed: String in ["", "supply_purchase", prefix + "3:6:18:extra", prefix + "6:03:18", prefix + "6:+3:18", prefix + "6:3.0:18", prefix + "6:3e0:18", prefix + "6:-3:18", prefix + "6:0:0", prefix + "6:3:019", prefix + "6:3:19", prefix + "10001:3:30003", prefix + "1:9223372036854775808:9223372036854775808", prefix + "2:9223372036854775807:9223372036854775807", "supply_purchase.auto.v2:19:ingredient/raw_food:6:3:18", "supply_purchase.auto.v1:019:ingredient/raw_food:6:3:18", "supply_purchase.auto.v1:18:ingredient/raw_food:6:3:18", "supply_purchase.auto.v1:9223372036854775808:ingredient/raw_food:6:3:18", "supply_purchase.auto.v1:1: :6:3:18"]:
|
|
_expect(SupplyAutoPurchasePlans.decode_receipt_memo(StringName(malformed)).is_empty(), "malformed/noncanonical/overflow quote memo is rejected: " + malformed)
|
|
var max_memo: StringName = &"supply_purchase.auto.v1:1:x:1:9223372036854775807:9223372036854775807"
|
|
_expect(SupplyAutoPurchasePlans.decode_receipt_memo(max_memo).get(&"unit_price", 0) == 9223372036854775807, "signed64 canonical maximum parses without floating conversion or overflow")
|
|
|
|
func _test_default_manual_and_paid_order() -> void:
|
|
_expect(_runtime.auto_purchase_plans.get_plans().is_empty(), "new game has no inferred automatic intent")
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 0 and _runtime.economy_service.create_snapshot() == before[&"systems"][&"runtime"][&"economy"], "default disabled tick spends and stocks nothing")
|
|
var manual: CommandResult = _runtime.submit_command(&"purchase_supplies", {&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 4})
|
|
_expect(manual.is_success() and int(manual.details.get(&"total_cost", -1)) == 12 and _stock() == 4, "manual contract still charges4 food exactly12G")
|
|
var transaction_id := StringName(manual.details.get(&"transaction_id", &""))
|
|
var entries: Array = _runtime.economy_service.get_transaction_entries(transaction_id)
|
|
_expect(entries.size() == 1 and entries[0].category == &"OPERATING_EXPENSE" and entries[0].memo_key == &"supply_purchase", "manual category/memo remain unchanged")
|
|
_expect(_runtime.economy_service.get_transaction_id_for_idempotency_key(StringName("supply.purchase.%d" % manual.command_id)) == transaction_id, "manual ledger key still uses accepted commandID")
|
|
var configured: CommandResult = _plan(true, 10, &"auto.test.intent")
|
|
_expect(configured.is_success() and _stock() == 4, "player intent does not immediately stock or pay")
|
|
var armed: Dictionary = _runtime.create_snapshot()
|
|
var cash: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
|
|
var notifications: Array[Dictionary] = []
|
|
_runtime.economy_service.transaction_posted.connect(func(id: StringName, ledger_rows: Array) -> void:
|
|
var rows: Array[Dictionary] = []
|
|
for entry: LedgerEntry in ledger_rows: rows.append(entry.to_dictionary())
|
|
notifications.append({&"transaction_id": id, &"entries": rows}))
|
|
_clock.advance_minutes(1)
|
|
var paid: Dictionary = _runtime.create_snapshot()
|
|
var row: Dictionary = _runtime.auto_purchase_plans.get_plan(_warehouse, SupplyCatalog.RAW_FOOD)
|
|
_expect(_stock() == 10 and _runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - 18, "ordinary tick buys the exact deficit6 at existing3G")
|
|
_expect(_state.get_resource(&"gold") == cash - 18 and row[&"last_result"] == &"PURCHASED" and row[&"last_attempt_minute"] == _clock.absolute_minute, "cash mirror and saved receipt record the real purchase")
|
|
var receipt := SupplyAutoPurchasePlans.receipt_from_ledger(_runtime.economy_service, _warehouse, SupplyCatalog.RAW_FOOD, _clock.absolute_minute, StringName(row[&"last_transaction_id"]))
|
|
_expect(receipt == {&"item_id": String(SupplyCatalog.RAW_FOOD), &"quantity": 6, &"unit_price": 3, &"total_cost": 18}, "independent actual ledger memo anchors item/quantity/historic price/total")
|
|
_expect(notifications.size() == 1 and notifications[0][&"transaction_id"] == StringName(row[&"last_transaction_id"]) and notifications[0][&"entries"].size() == 1 and notifications[0][&"entries"][0][&"memo_key"] == String(SupplyAutoPurchasePlans.receipt_memo(SupplyCatalog.RAW_FOOD, 6, 3, 18)), "normal successful automatic purchase delivers one actual quoted ledger notification")
|
|
_expect((_runtime.economy_service.get("_pending_transaction_notifications") as Dictionary).is_empty(), "normal automatic notification is committed without pending residue")
|
|
_expect(paid[&"systems"][&"runtime"][&"next_command_id"] == armed[&"systems"][&"runtime"][&"next_command_id"] and var_to_bytes(paid[&"systems"][&"runtime"][&"command_queue"]) == var_to_bytes(armed[&"systems"][&"runtime"][&"command_queue"]), "automatic order allocates no fake player command or queue history")
|
|
_expect(var_to_bytes(paid[&"systems"][&"runtime"][&"rng_streams"]) == var_to_bytes(armed[&"systems"][&"runtime"][&"rng_streams"]), "automatic purchase makes no RNG draw")
|
|
_expect(_runtime.supply_management.advance_auto_purchases(_runtime, _clock.absolute_minute).is_empty() and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "same-minute reentry is exact no-op")
|
|
_expect(notifications.size() == 1, "same-minute automatic reentry does not duplicate actual notification")
|
|
_expect(_runtime.restore_snapshot(armed), "whole pre-order snapshot restores")
|
|
_clock.advance_minutes(1)
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "saved pre-order branch replays all typed state/order/events/IDs exactly")
|
|
_expect(_runtime.restore_snapshot(paid) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "paid order/current14 receipt roundtrip is exact")
|
|
var duplicate_before: Dictionary = _runtime.create_snapshot()
|
|
var duplicate: CommandResult = _plan(true, 10, &"auto.test.intent")
|
|
_expect(not duplicate.is_success() and duplicate.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY and not duplicate.is_replay and _stock() == 10, "already accepted intent key is rejected by the canonical command queue")
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(duplicate_before), "duplicate intent refusal changes no plan, inventory, ledger, IDs, queue or Runtime state")
|
|
var owned: Dictionary = _runtime.auto_purchase_plans.get_plan(_warehouse, SupplyCatalog.RAW_FOOD)
|
|
owned[&"enabled"] = false
|
|
_expect(_runtime.auto_purchase_plans.get_plan(_warehouse, SupplyCatalog.RAW_FOOD)[&"enabled"], "returned plan is owned")
|
|
|
|
func _test_invalid_input_and_corrupt_save() -> void:
|
|
_expect(_runtime.restore_snapshot(_built), "reset to paid real warehouse fixture")
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
for value: Variant in [true, -1, 101, 1.5, "4", NAN, INF]:
|
|
var result: CommandResult = _runtime.supply_management.set_auto_purchase(_runtime,
|
|
SimulationCommand.new(999, _clock.absolute_minute, _clock.absolute_minute, 75,
|
|
&"set_supply_auto_purchase", &"player", _warehouse,
|
|
{&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": value}))
|
|
_expect(not result.is_success() and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(before), "invalidtarget rejects atomically: %s" % value)
|
|
for patch: Dictionary in [
|
|
{&"warehouse_id": &"missing", &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": 1},
|
|
{&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.BASIC_MEAL, &"enabled": true, &"target_stock": 1},
|
|
{&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": "true", &"target_stock": 1},
|
|
{&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": 0},
|
|
{&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": 1, &"unit_price": 0}]:
|
|
var result: CommandResult = _runtime.supply_management.set_auto_purchase(_runtime, SimulationCommand.new(999,0,0,75,&"set_supply_auto_purchase",&"player",_warehouse,patch))
|
|
_expect(not result.is_success() and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(before), "forged/wrongintent changes no state")
|
|
_expect(_plan(true, 6).is_success(), "configure a canonical saved plan")
|
|
var canonical: Dictionary = _runtime.create_snapshot()
|
|
for mutation: String in ["missing", "version", "duplicate", "unknown", "target", "bool", "clock", "receipt", "price", "extra"]:
|
|
var bad: Dictionary = canonical.duplicate(true)
|
|
var data: Dictionary = bad[&"systems"][&"runtime"]
|
|
var plans: Dictionary = data[&"auto_purchase_plans"]
|
|
var row: Dictionary = plans[&"plans"][0]
|
|
match mutation:
|
|
"missing": data.erase(&"auto_purchase_plans")
|
|
"version": plans[&"version"] = 2
|
|
"duplicate": (plans[&"plans"] as Array).append(row.duplicate(true))
|
|
"unknown": row[&"warehouse_id"] = "missing"
|
|
"target": row[&"target_stock"] = 101
|
|
"bool": row[&"enabled"] = 1
|
|
"clock": row[&"last_attempt_minute"] = _clock.absolute_minute + 1
|
|
"receipt": row[&"last_transaction_id"] = "txn.foreign"
|
|
"price": row[&"last_unit_price"] = 0.0
|
|
"extra": row[&"unit_price"] = 0
|
|
_expect(not _runtime.restore_snapshot(bad) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(canonical), "corrupt14 plan rejected without mutation: " + mutation)
|
|
|
|
func _test_atomic_failures_and_same_minute_retry() -> void:
|
|
for kind: String in ["payment", "notification", "partial_stock"]:
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "configure independent failure fixture: " + kind)
|
|
var economy_before: Dictionary = _runtime.economy_service.create_snapshot()
|
|
var inventory_before: Dictionary = _runtime.inventory_service.create_snapshot()
|
|
var probe: ProbedInventory = PartialFailInventory.new() if kind == "partial_stock" else ProbedInventory.new()
|
|
_expect(probe.restore_snapshot(inventory_before), "Inventory probe starts from full actual snapshot: " + kind)
|
|
_runtime.inventory_service = probe
|
|
if kind != "partial_stock":
|
|
var rejecting: EconomyService = RejectingPaymentEconomy.new() if kind == "payment" else RejectingNotificationEconomy.new()
|
|
_expect(rejecting.restore_snapshot(economy_before), "injected Economy starts from actual snapshot")
|
|
_runtime.economy_service = rejecting
|
|
_runtime.call("_connect_economy_signals")
|
|
var notifications: Array[StringName] = []
|
|
_runtime.economy_service.transaction_posted.connect(func(id: StringName, _entries: Array) -> void: notifications.append(id))
|
|
var pending_before: Dictionary = (_runtime.economy_service.get("_pending_transaction_notifications") as Dictionary).duplicate(true)
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 0 and var_to_bytes(_runtime.economy_service.create_snapshot()) == var_to_bytes(economy_before), "failed order keeps actual cash and all ledger bytes unchanged: " + kind)
|
|
_expect(not probe.transaction_boundary.is_empty() and var_to_bytes(_runtime.inventory_service.create_snapshot()) == var_to_bytes(probe.transaction_boundary), "transaction-boundary rollback restores full Inventory IDs/reservations/history/order, allowing the preceding ordinary clock advancement: " + kind)
|
|
_expect(probe.transaction_boundary.get(&"current_minute", -1) == _clock.absolute_minute and _clock.absolute_minute > int(inventory_before[&"current_minute"]), "Inventory probe observes the actual next-minute transaction boundary: " + kind)
|
|
_expect(notifications.is_empty() and var_to_bytes(_runtime.economy_service.get("_pending_transaction_notifications")) == var_to_bytes(pending_before), "failed automatic transaction emits no notification and leaves the observed pending set exact: " + kind)
|
|
var failed: Dictionary = _runtime.create_snapshot()
|
|
var row: Dictionary = _runtime.auto_purchase_plans.get_plan(_warehouse, SupplyCatalog.RAW_FOOD)
|
|
_expect(row[&"last_attempt_minute"] == _clock.absolute_minute and row[&"last_result"] != &"PURCHASED", "actual failure stays recorded in same minute: " + kind)
|
|
_expect(_runtime.restore_snapshot(failed), "recorded failure restores into regular services: " + kind)
|
|
_expect(_runtime.supply_management.advance_auto_purchases(_runtime, _clock.absolute_minute).is_empty() and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(failed), "same-minute regular retry cannot hide the actual failure: " + kind)
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 6 and _runtime.economy_service.get_balance(&"PLAYER_CASH") == int(_built[&"game_state"][&"resources"][&"gold"]) - 18, "next ordinary tick retries exactly once: " + kind)
|
|
|
|
func _test_hold_disable_and_removal() -> void:
|
|
_expect(_runtime.restore_snapshot(_built), "hold fixture restores")
|
|
_expect(_plan(true, 6).is_success(), "enable before normal closure")
|
|
_expect(_runtime.submit_command(&"set_facility_policy", {&"facility_id": _warehouse, &"patch": {&"business_open": false}}).is_success(), "normal closure precedes auto tick")
|
|
var cash: int = _state.get_resource(&"gold")
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 0 and _state.get_resource(&"gold") == cash, "closed warehouse does not stock/pay")
|
|
_expect(_plan(false, 0).is_success(), "setting disable works despite closed receiver")
|
|
_expect(_runtime.submit_command(&"set_facility_policy", {&"facility_id": _warehouse, &"patch": {&"business_open": true}}).is_success(), "normal opening restores logistical eligibility")
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 0, "disabled plan stays off after reopening")
|
|
_expect(_plan(true, 6).is_success(), "enable again")
|
|
_clock.advance_minutes(1)
|
|
var paid: Dictionary = _runtime.create_snapshot()
|
|
var removal: CommandResult = _runtime.submit_command(&"demolish_facility", {&"instance_id": _warehouse})
|
|
_expect(not removal.is_success() and _runtime.auto_purchase_plans.create_snapshot() == paid[&"systems"][&"runtime"][&"auto_purchase_plans"], "stock-bearing failed removal preserves plan")
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "future closure fixture resets")
|
|
cash = _state.get_resource(&"gold")
|
|
_expect(_runtime.submit_command(&"set_facility_policy", {&"facility_id": _warehouse, &"patch": {&"business_open": false}, &"effective_at": _clock.absolute_minute + 1}).is_success(), "normal future closure is scheduled for next tick")
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 0 and _state.get_resource(&"gold") == cash and not _runtime.facility_service.get_instance(_warehouse).operator_open, "due closure blocks auto purchase before FacilityCompletion without early policy mutation")
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "empty warehouse with explicit intent exists")
|
|
_expect(_runtime.submit_command(&"demolish_facility", {&"instance_id": _warehouse}).is_success() and _runtime.auto_purchase_plans.get_plans().is_empty(), "successful empty warehouse removal prunes its intent")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "removed warehouse history/ledger remains restorable")
|
|
|
|
func _test_actual_budget_power_reservation_and_terminal() -> void:
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "configure a paid budget boundary")
|
|
var cash: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
|
|
_expect(_runtime.economy_service.transfer(&"PLAYER_CASH", &"CONSTRUCTION_EXPENSE", cash - 17, _clock.absolute_minute, &"CONSTRUCTION", &"", &"auto.acceptance.budget").is_success(), "disclosed ledger cash fixture leaves17G")
|
|
_clock.advance_minutes(1)
|
|
var row: Dictionary = _runtime.supply_management.auto_purchase_report(_runtime, _warehouse, SupplyCatalog.RAW_FOOD)
|
|
_expect(_stock() == 0 and _state.get_resource(&"gold") == 17 and row[&"reason"] == &"INSUFFICIENT_FUNDS", "full deficit18G never silently becomes partial free or affordable17G order")
|
|
_expect(row[&"last_attempt_minute"] == -1, "ineligible quote does not fabricate an executed order")
|
|
_expect(_runtime.economy_service.transfer(&"CONSTRUCTION_EXPENSE", &"PLAYER_CASH", 1, _clock.absolute_minute, &"CONSTRUCTION", &"", &"auto.acceptance.budget.refund").is_success(), "same actual ledger can return1G")
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 6 and _state.get_resource(&"gold") == 0, "eligible full order charges exactly available18G")
|
|
var paid: Dictionary = _runtime.create_snapshot()
|
|
var manual_key: StringName = &"auto.acceptance.manual.replacement"
|
|
var manual: CommandResult = _runtime.submit_command(&"purchase_supplies", {&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 1}, &"player", _warehouse, 75, -1, manual_key)
|
|
_expect(not manual.is_success(), "manual insufficient cash remains rejected")
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "reserved-stock fixture resets")
|
|
_expect(_runtime.submit_command(&"purchase_supplies", {&"warehouse_id": _warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 6}).is_success(), "reserved stock starts from an actual paid order")
|
|
var facility: FacilityInstance = _runtime.facility_service.get_instance(_warehouse)
|
|
var reservation: Dictionary = _runtime.inventory_service.reserve(facility.local_inventory_id, SupplyCatalog.RAW_FOOD, 6, &"disclosed.reserve.fixture", &"TEST", _clock.absolute_minute)
|
|
_expect(reservation.get(&"ok", false), "disclosed Inventory reservation retains physical stock")
|
|
cash = _state.get_resource(&"gold")
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 6 and _runtime.inventory_service.available_quantity(facility.local_inventory_id, SupplyCatalog.RAW_FOOD) == 0 and _state.get_resource(&"gold") == cash, "reserved physical units are not mistaken for missing stock or double-purchased")
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "power boundary resets")
|
|
_state.set_resource(&"mana", 0) # Disclosed resource boundary, not natural Mana generation.
|
|
cash = _state.get_resource(&"gold")
|
|
_clock.advance_minutes(1)
|
|
_expect(_stock() == 0 and _state.get_resource(&"gold") == cash, "Mana0 stops automatic paid order")
|
|
_expect(_runtime.restore_snapshot(_built) and _plan(true, 6).is_success(), "paused real-time boundary resets")
|
|
var paused: Dictionary = _runtime.create_snapshot()
|
|
_expect(_clock.process_real_delta(60.0) == 0 and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paused), "paused real clock causes no automatic purchase or ID/timer change")
|
|
var collision_key: StringName = SupplyAutoPurchasePlans.order_key(_warehouse, SupplyCatalog.RAW_FOOD, _clock.absolute_minute)
|
|
_expect(_runtime.economy_service.transfer(&"PLAYER_CASH", &"CONSTRUCTION_EXPENSE", 1, _clock.absolute_minute, &"CONSTRUCTION", _warehouse, collision_key).is_success(), "disclosed foreign ledger-key fixture uses a valid ordinary transfer")
|
|
cash = _state.get_resource(&"gold")
|
|
var collisions: Array = _runtime.supply_management.advance_auto_purchases(_runtime, _clock.absolute_minute)
|
|
_expect(collisions.size() == 1 and _stock() == 0 and _state.get_resource(&"gold") == cash and _runtime.auto_purchase_plans.get_plan(_warehouse, SupplyCatalog.RAW_FOOD)[&"last_result"] == &"REPLAY_MISMATCH", "foreign ledger namespace cannot be treated as a free stocked purchase")
|
|
var collision: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.restore_snapshot(collision) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(collision), "foreign-key failure receipt remains restorable without making it purchase income")
|
|
_expect(_runtime.restore_snapshot(paid), "restore an actual paid receipt for corruption tests")
|
|
var corrupt: Dictionary = paid.duplicate(true)
|
|
corrupt[&"systems"][&"runtime"][&"auto_purchase_plans"][&"plans"][0][&"last_transaction_id"] = String(_runtime.economy_service.get_entries()[0].transaction_id)
|
|
_expect(not _runtime.restore_snapshot(corrupt) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "an existing unrelated actual transaction cannot replace the automatic receipt")
|
|
for field: StringName in [&"last_quantity", &"last_unit_price", &"last_total_cost"]:
|
|
corrupt = paid.duplicate(true)
|
|
corrupt[&"systems"][&"runtime"][&"auto_purchase_plans"][&"plans"][0][field] += 1
|
|
_expect(not _runtime.restore_snapshot(corrupt) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "saved auto quantity/price/total quote cannot disagree with payment: %s" % field)
|
|
for pair: Vector2i in [Vector2i(3, 6), Vector2i(2, 9), Vector2i(1, 18), Vector2i(9, 2)]:
|
|
corrupt = paid.duplicate(true)
|
|
var changed_row: Dictionary = corrupt[&"systems"][&"runtime"][&"auto_purchase_plans"][&"plans"][0]
|
|
changed_row[&"last_quantity"] = pair.x
|
|
changed_row[&"last_unit_price"] = pair.y
|
|
_expect(not _runtime.restore_snapshot(corrupt) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "coordinated factorization retaining actual18G cannot rewrite paid6x3G quote: %s" % pair)
|
|
var receipt_id: String = paid[&"systems"][&"runtime"][&"auto_purchase_plans"][&"plans"][0][&"last_transaction_id"]
|
|
for malformed: StringName in [&"supply_purchase", SupplyAutoPurchasePlans.receipt_memo(SupplyCatalog.BASIC_MEAL, 6, 3, 18), SupplyAutoPurchasePlans.receipt_memo(SupplyCatalog.RAW_FOOD, 3, 6, 18), &"supply_purchase.auto.v1:19:ingredient/raw_food:6:3:18:extra"]:
|
|
corrupt = paid.duplicate(true)
|
|
for entry: Dictionary in corrupt[&"systems"][&"runtime"][&"economy"][&"entries"]:
|
|
if entry[&"transaction_id"] == receipt_id: entry[&"memo_key"] = String(malformed)
|
|
_expect(not _runtime.restore_snapshot(corrupt) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(paid), "ledger quote missing/wrong item/re-factored/noncanonical is rejected against independent plan atomically: %s" % malformed)
|
|
# Operations can only be enabled at fresh Day1 06:00 (360), before building.
|
|
# Keep the normal paid construction and intent after that canonical start.
|
|
_clock.reset(1, 6, 0)
|
|
_state.reset()
|
|
_expect(_runtime.initialize(_clock, _state, 420_501)[&"valid"], "fresh canonical bankruptcy fixture initializes")
|
|
_clock.set_paused(true)
|
|
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
var started: Dictionary = _runtime.enable_production_operations()
|
|
_expect(started[&"ok"] and _clock.absolute_minute == 360, "actual bankruptcy boundary enables ordinary daily operation at fresh Day1 06:00")
|
|
_expect(_runtime.submit_command(&"build_facility", {&"definition_id": &"warehouse", &"origin": Vector3i(-8, 0, 1)}).is_success(), "terminal fixture uses ordinary paid warehouse construction")
|
|
_clock.advance_minutes(180)
|
|
_warehouse = &""
|
|
for endpoint: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if endpoint.definition_id == &"warehouse": _warehouse = endpoint.instance_id
|
|
_expect(not _warehouse.is_empty() and _plan(true, 6).is_success(), "terminal fixture configures explicit player intent after ordinary warehouse completion")
|
|
_clock.advance_minutes(4320 - _clock.absolute_minute)
|
|
_expect(_runtime.operations_coordinator.get_day_summaries().size() == 3 and _runtime.operations_coordinator.get_terminal_state() == &"BANKRUPT", "warehouse-only actual costs reach existing three-deficit terminal rule")
|
|
var terminal: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.supply_management.advance_auto_purchases(_runtime, _clock.absolute_minute + 1).is_empty() and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(terminal), "BANKRUPT blocks every further internal purchase without fake commands")
|
|
_expect(_runtime.restore_snapshot(terminal), "terminal intent/ledger/current14 save still restores")
|
|
|
|
func _test_multiple_warehouse_order() -> void:
|
|
_expect(_runtime.restore_snapshot(_built), "multiple actual warehouses fixture starts with one paid unit")
|
|
_expect(_runtime.submit_command(&"build_facility", {&"definition_id": &"warehouse", &"origin": Vector3i(2,0,1)}).is_success(), "ordinary second warehouse construction is paid")
|
|
_clock.advance_minutes(180)
|
|
var ids: Array[StringName] = []
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"warehouse": ids.append(facility.instance_id)
|
|
ids.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b))
|
|
_expect(ids.size() == 2, "two actual warehouse endpoints exist")
|
|
if ids.size() != 2: return
|
|
for index: int in [1, 0]:
|
|
_expect(_runtime.submit_command(&"set_supply_auto_purchase", {&"warehouse_id": ids[index], &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": 1}).is_success(), "reverse player setting order does not define payment priority")
|
|
var cash: int = _state.get_resource(&"gold")
|
|
_expect(_runtime.economy_service.transfer(&"PLAYER_CASH", &"CONSTRUCTION_EXPENSE", cash - 3, _clock.absolute_minute, &"CONSTRUCTION", &"", &"auto.acceptance.multi_budget").is_success(), "disclosed shared cash fixture funds exactly one existing3G unit")
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
_clock.advance_minutes(1)
|
|
var after: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.inventory_service.total_quantity(_runtime.facility_service.get_instance(ids[0]).local_inventory_id, SupplyCatalog.RAW_FOOD) == 1 and _runtime.inventory_service.total_quantity(_runtime.facility_service.get_instance(ids[1]).local_inventory_id, SupplyCatalog.RAW_FOOD) == 0, "explicit lexicographic warehouse/item ordering deterministically spends shared cash once")
|
|
_expect(_runtime.restore_snapshot(before), "multiple-plan pre-order snapshot restores")
|
|
_clock.advance_minutes(1)
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(after), "restored multiple-plan allocation reproduces every typed field and event in order")
|
|
|
|
func _test_legacy13_and_composed_rollback() -> void:
|
|
_expect(_runtime.restore_snapshot(_built), "legacy fixture resets")
|
|
var legacy: Dictionary = _built.duplicate(true)
|
|
legacy[&"systems"][&"runtime"][&"runtime_schema_version"] = 13
|
|
legacy[&"systems"][&"runtime"].erase(&"auto_purchase_plans")
|
|
_expect(_runtime.restore_snapshot(legacy) and _runtime.auto_purchase_plans.get_plans().is_empty(), "valid13 snapshot migrates to disabled14 without purchase inference")
|
|
var migrated: Dictionary = _runtime.create_snapshot()
|
|
var disguised: Dictionary = legacy.duplicate(true)
|
|
disguised[&"systems"][&"runtime"][&"auto_purchase_plans"] = {&"version": 1, &"plans": []}
|
|
_expect(not _runtime.restore_snapshot(disguised) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(migrated), "oldversion cannot covertly retain new aggregate")
|
|
_clock.set_paused(false)
|
|
_expect(_plan(true, 6).is_success(), "actual current14 pending automatic intent queues")
|
|
_clock.set_paused(true)
|
|
var pending: Dictionary = _runtime.create_snapshot()
|
|
var disguised_pending: Dictionary = pending.duplicate(true)
|
|
disguised_pending[&"systems"][&"runtime"][&"runtime_schema_version"] = 13
|
|
disguised_pending[&"systems"][&"runtime"].erase(&"auto_purchase_plans")
|
|
_expect(not _runtime.restore_snapshot(disguised_pending) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(pending), "old13 cannot reinterpret a covert queued automatic order as paid feature intent")
|
|
_expect(_runtime.restore_snapshot(_built), "reset after pending downgrade refusal")
|
|
_expect(_plan(true, 6).is_success(), "intent exists before composed rollback token")
|
|
var token: Dictionary = _runtime.call("_capture_operations_external_state")
|
|
_expect(not token.is_empty() and token.has(&"auto_purchase_plans"), "Operations rollback includes owned plan state")
|
|
_expect(_runtime.auto_purchase_plans.set_plan(_warehouse, SupplyCatalog.RAW_FOOD, false, 0, _runtime.facility_service)[&"ok"], "disclosed boundary mutation fixture changes only owned intent")
|
|
_expect(_runtime.call("_restore_operations_external_state", token) and _runtime.call("_operations_external_state_matches", token), "composed rollback restores exact plan and other participants")
|
|
_runtime.call("_discard_operations_domain_events")
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
_checks += 1
|
|
if not condition:
|
|
_failures += 1
|
|
push_error("FAIL: " + message)
|
|
|
|
func _finish() -> void:
|
|
print("SUPPLY_AUTO_PURCHASE checks=%d failures=%d" % [_checks, _failures])
|
|
quit(_failures)
|