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.
267 lines
14 KiB
GDScript
267 lines
14 KiB
GDScript
extends SceneTree
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
var _clock: Variant
|
|
var _state: Variant
|
|
var _runtime: Variant
|
|
var _facility_id: StringName
|
|
var _facility_inventory_id: StringName
|
|
var _kitchen_fixture_gold := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
print("S2: runtime supply-to-service cycle")
|
|
_reset_runtime()
|
|
_build_operational_cafeteria()
|
|
_deliver_inputs_through_logistics()
|
|
_run_service_sale_with_round_trip()
|
|
_test_power_projection()
|
|
_cleanup()
|
|
if _failures == 0:
|
|
print("PASS: %d checks" % _checks)
|
|
else:
|
|
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
|
quit(_failures)
|
|
|
|
|
|
func _reset_runtime() -> void:
|
|
_cleanup()
|
|
_clock = load("res://autoload/game_clock.gd").new()
|
|
_state = load("res://autoload/game_state.gd").new()
|
|
_runtime = load("res://autoload/simulation_runtime.gd").new()
|
|
_clock.reset(1, 6, 0)
|
|
_state.reset()
|
|
var report: Dictionary = _runtime.initialize(_clock, _state, 2202)
|
|
_clock.set_paused(true)
|
|
_expect(report[&"valid"], "runtime initializes all S2 definitions")
|
|
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 5000, "starting Gold is initialized through the ledger")
|
|
_expect(_runtime.economy_service.validate_ledger()[&"ok"], "initial ledger reconciles")
|
|
|
|
|
|
func _cleanup() -> void:
|
|
if _runtime != null:
|
|
_runtime.free()
|
|
if _clock != null:
|
|
_clock.free()
|
|
if _state != null:
|
|
_state.free()
|
|
_runtime = null
|
|
_clock = null
|
|
_state = null
|
|
_kitchen_fixture_gold = 0
|
|
|
|
|
|
func _build_operational_cafeteria() -> void:
|
|
var build = _runtime.submit_command(
|
|
&"build_facility",
|
|
{&"definition_id": &"cafeteria", &"origin": Vector3i(-5, 0, WorldService.LAYER_FRONT), &"rotation": 0},
|
|
&"player", &"", 50, -1, &"s2.build.cafeteria"
|
|
)
|
|
_expect(build.is_success(), "cafeteria construction starts")
|
|
_clock.advance_minutes(180)
|
|
var facilities: Array[Dictionary] = _runtime.construction_service.get_facilities()
|
|
_expect(facilities.size() == 1, "construction produces one authoritative facility")
|
|
_facility_id = StringName(facilities[0][&"instance_id"])
|
|
var operational = _runtime.facility_service.get_instance(_facility_id)
|
|
_expect(operational != null and operational.definition_id == &"cafeteria", "completed construction registers with FacilityService")
|
|
_expect(operational.state == FacilityInstance.State.NO_STAFF, "staff requirement prevents premature opening")
|
|
_expect(_runtime.economy_service.has_account(StringName("FACILITY_REVENUE:%s" % _facility_id)), "facility receives a dedicated revenue account")
|
|
_expect(not _runtime.mana_network_service.get_node(_facility_id).is_empty(), "Mana demand is registered for the completed facility")
|
|
_facility_inventory_id = operational.local_inventory_id
|
|
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, _facility_id, false)
|
|
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid kitchen setup/link for logical-attendance S2 fixture")
|
|
_kitchen_fixture_gold = int(kitchen_setup.get(&"gold_cost", -1))
|
|
var staffing = _runtime.submit_command(
|
|
&"set_facility_conditions",
|
|
{&"facility_id": _facility_id, &"conditions": {&"active_staff_ids": [&"cook_1", &"server_1"]}},
|
|
&"manager", _facility_id, 20, -1, &"s2.staff.cafeteria"
|
|
)
|
|
_expect(staffing.is_success(), "two active staff can be assigned through a command")
|
|
_expect(operational.state == FacilityInstance.State.OPEN, "staffed and powered cafeteria becomes operational")
|
|
_expect(_state.get_resource(&"gold") == 4200 - _kitchen_fixture_gold, "construction Gold is paid once through the authoritative ledger")
|
|
_expect(_runtime.economy_service.get_balance(&"CONSTRUCTION_EXPENSE") == 800 + _kitchen_fixture_gold, "construction counter-account retains the capital flow")
|
|
|
|
|
|
func _deliver_inputs_through_logistics() -> void:
|
|
var stocked = _runtime.submit_command(
|
|
&"add_inventory_stack",
|
|
{
|
|
&"owner_inventory_id": &"supplier.main",
|
|
&"item_id": &"ingredient/basic_meal",
|
|
&"quantity": 3,
|
|
&"quality": 70,
|
|
&"unit_weight": 1,
|
|
},
|
|
&"supplier", &"supplier.main", 10, -1, &"s2.stock.ingredients"
|
|
)
|
|
_expect(stocked.is_success(), "supplier stock enters InventoryService")
|
|
var created = _runtime.submit_command(
|
|
&"create_logistics_request",
|
|
{
|
|
&"request_id": &"delivery_meals_1",
|
|
&"source_inventory_id": &"supplier.main",
|
|
&"destination_inventory_id": _facility_inventory_id,
|
|
&"item_requirements": {&"ingredient/basic_meal": 2},
|
|
&"priority": 80,
|
|
&"deadline": _clock.absolute_minute + 120,
|
|
},
|
|
&"manager", _facility_id, 10, -1, &"s2.logistics.create"
|
|
)
|
|
_expect(created.is_success(), "logistics request reserves supplier stock")
|
|
var logistics: LogisticsRequest = _runtime.logistics_requests[&"delivery_meals_1"]
|
|
_expect(logistics.state == LogisticsRequest.State.RESERVED and logistics.total_weight == 2, "request records reserved weight and state")
|
|
_expect(_runtime.inventory_service.available_quantity(&"supplier.main", &"ingredient/basic_meal") == 1, "reserved supplier stock cannot be reused")
|
|
|
|
var dispatched = _runtime.submit_command(
|
|
&"dispatch_logistics",
|
|
{&"carriers": {
|
|
&"porter_1": {&"capacity": 10, &"current_load": 0, &"path_minutes": 2, &"role": &"logistics"},
|
|
&"porter_2": {&"capacity": 10, &"current_load": 0, &"path_minutes": 4, &"role": &"carrier"},
|
|
}},
|
|
&"dispatcher", &"delivery_meals_1", 10, -1, &"s2.logistics.dispatch"
|
|
)
|
|
_expect(dispatched.is_success(), "dispatcher evaluates the reserved request")
|
|
_expect(logistics.assigned_carrier_id == &"porter_1", "deterministic score chooses the suitable nearest carrier")
|
|
_expect(logistics.state == LogisticsRequest.State.ASSIGNED, "dispatch advances request to ASSIGNED")
|
|
|
|
var start_pickup = _advance_delivery(&"s2.logistics.to_pickup")
|
|
_expect(start_pickup.is_success() and logistics.state == LogisticsRequest.State.PICKING_UP, "assigned carrier begins pickup")
|
|
var picked_up = _advance_delivery(&"s2.logistics.pickup")
|
|
_expect(picked_up.is_success() and logistics.state == LogisticsRequest.State.DELIVERING, "source arrival transfers reserved stock into cargo")
|
|
_expect(_runtime.inventory_service.total_quantity(&"cargo:porter_1", &"ingredient/basic_meal") == 2, "cargo owns exactly the picked quantity")
|
|
_expect(_runtime.inventory_service.total_quantity(&"supplier.main", &"ingredient/basic_meal") == 1, "pickup subtracts source stock without duplication")
|
|
var delivered = _advance_delivery(&"s2.logistics.delivery")
|
|
_expect(delivered.is_success() and logistics.state == LogisticsRequest.State.COMPLETED, "destination arrival completes logistics")
|
|
_expect(_runtime.inventory_service.total_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 2, "facility inventory receives the delivered inputs")
|
|
_expect(_runtime.inventory_service.total_quantity(&"cargo:porter_1", &"ingredient/basic_meal") == 0, "completed delivery leaves no duplicated cargo")
|
|
_expect(_runtime.logistics_dispatcher.get_reserved_load(&"porter_1") == 0, "carrier capacity releases on completion")
|
|
_expect(_runtime.inventory_service.validate_invariants()[&"ok"], "delivery preserves every inventory invariant")
|
|
|
|
|
|
func _advance_delivery(idempotency_key: StringName) -> CommandResult:
|
|
return _runtime.submit_command(
|
|
&"advance_logistics_request",
|
|
{&"request_id": &"delivery_meals_1"},
|
|
&"porter_1", &"delivery_meals_1", 10, -1, idempotency_key
|
|
)
|
|
|
|
|
|
func _run_service_sale_with_round_trip() -> void:
|
|
var wallet = _runtime.submit_command(
|
|
&"register_guest_wallet",
|
|
{&"guest_id": &"guest_1", &"gold": 100},
|
|
&"guest_1", &"guest_1", 10, -1, &"s2.guest.wallet"
|
|
)
|
|
_expect(wallet.is_success(), "guest wallet is initialized through SYSTEM_SOURCE")
|
|
var requested = _runtime.submit_command(
|
|
&"create_service_request",
|
|
{
|
|
&"request_id": &"meal_request_1",
|
|
&"facility_id": _facility_id,
|
|
&"guest_ids": [&"guest_1"],
|
|
&"service_type": &"meal",
|
|
&"options": {&"service_minutes": 3},
|
|
},
|
|
&"guest_1", _facility_id, 10, -1, &"s2.service.request"
|
|
)
|
|
_expect(requested.is_success(), "service request reserves its recipe input and enters the queue")
|
|
_expect(_runtime.inventory_service.available_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 1, "queued service protects one ingredient from reuse")
|
|
_clock.advance_one_minute()
|
|
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
|
|
_expect(active.size() == 1, "facility queue fills an available service slot")
|
|
var service_id: StringName = active[0].service_id
|
|
_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)
|
|
_expect(completed != null and completed.state == ServiceInstance.State.COMPLETED, "three active minutes complete the service")
|
|
_expect(_runtime.inventory_service.total_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 1, "completion consumes exactly one reserved ingredient")
|
|
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_1") == 75, "guest pays the immutable quoted price")
|
|
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4225 - _kitchen_fixture_gold, "service cash sweep increases player Gold")
|
|
_expect(_state.get_resource(&"gold") == 4225 - _kitchen_fixture_gold, "UI Gold mirror follows the ledger transaction")
|
|
_expect(_runtime.economy_service.validate_ledger()[&"ok"], "service sale remains double-entry balanced")
|
|
var saw_completion := false
|
|
for event in _runtime.domain_events.recent_events():
|
|
if event.event_type == &"SERVICE_COMPLETED" and service_id in event.entity_ids:
|
|
saw_completion = true
|
|
_expect(saw_completion, "service completion is projected as a domain event")
|
|
var final_digest: String = _runtime.latest_digest
|
|
|
|
_expect(_runtime.restore_snapshot(mid_snapshot), "mid-service snapshot restores all S2 services")
|
|
_expect(_runtime.latest_digest == mid_digest, "mid-service restore reproduces the exact digest")
|
|
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_1") == 100, "restore rolls payment back to the pre-completion ledger")
|
|
_clock.advance_minutes(3)
|
|
_expect(_runtime.latest_digest == final_digest, "replaying completion reproduces inventory, ledger, events and IDs exactly")
|
|
_expect(_runtime.economy_service.get_entries().filter(func(entry: LedgerEntry) -> bool: return entry.category == &"SERVICE_SALE").size() == 1, "replayed service sale is recorded exactly once")
|
|
|
|
var no_stock = _runtime.submit_command(
|
|
&"create_service_request",
|
|
{
|
|
&"request_id": &"meal_request_too_many",
|
|
&"facility_id": _facility_id,
|
|
&"guest_ids": [&"guest_1"],
|
|
&"options": {&"service_minutes": 20},
|
|
},
|
|
&"guest_1", _facility_id, 10, -1, &"s2.service.second"
|
|
)
|
|
_expect(no_stock.is_success(), "remaining delivered input can fund one more queued service")
|
|
var before_rejected: Dictionary = _runtime.inventory_service.create_snapshot()
|
|
var rejected = _runtime.submit_command(
|
|
&"create_service_request",
|
|
{
|
|
&"request_id": &"meal_request_no_stock",
|
|
&"facility_id": _facility_id,
|
|
&"guest_ids": [&"guest_1"],
|
|
},
|
|
&"guest_1", _facility_id, 10, -1, &"s2.service.no_stock"
|
|
)
|
|
_expect(not rejected.is_success(), "service without available recipe input is rejected")
|
|
_expect(_runtime.inventory_service.create_snapshot() == before_rejected, "rejected service request leaves reservations and stock unchanged")
|
|
|
|
|
|
func _test_power_projection() -> void:
|
|
_runtime.mana_network_service.set_source_output(_runtime.STARTER_MANA_SOURCE, 0)
|
|
var minutes_to_boundary: int = 10 - (_clock.absolute_minute % 10)
|
|
_clock.advance_minutes(minutes_to_boundary)
|
|
var facility = _runtime.facility_service.get_instance(_facility_id)
|
|
_expect(_runtime.mana_network_service.get_consumer_state(_facility_id) == ManaNetworkService.ConsumerState.NO_POWER, "Mana shortage shuts the cafeteria consumer down")
|
|
_expect(facility.state == FacilityInstance.State.NO_POWER, "Mana consumer state projects into FacilityService")
|
|
var interrupted_request_id := &"meal_request_too_many"
|
|
var interrupted_services: Array = _runtime.facility_service.get_active_services(_facility_id)
|
|
_expect(
|
|
_runtime.facility_service.get_request(interrupted_request_id) != null \
|
|
and interrupted_services.size() == 1 \
|
|
and interrupted_services[0].state == ServiceInstance.State.PAUSED,
|
|
"power loss safely pauses the active service without losing its request"
|
|
)
|
|
|
|
|
|
func _expect(condition: bool, label: String) -> void:
|
|
_checks += 1
|
|
if condition:
|
|
print(" ✓ %s" % label)
|
|
return
|
|
_failures += 1
|
|
push_error(" ✗ %s" % label)
|