Files
TheTree_mock/tests/s2/test_runtime_operations_cycle.gd
T
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
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.
2026-10-11 15:58:00 +09:00

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)