extends SceneTree const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd") ## P05-02: front (表金) and hidden (裏金) funds, the A09 payment rule, the A10 rank ## tax, the A11 loan interest, the three business day reconciliation and saves. ## Sales, debt and drains are explicit ledger fixtures; the daily closes are the ## production Operations boundaries. The last check opens the ledger from the ## game's own Main (F7), so the runner gives this suite the ordinary Main ## audio cleanup proof (tools/regression/suite-policy.json). const FRONT := &"PLAYER_CASH" const BACK := &"BACK_CASH" const LOAN := &"LOAN_LIABILITY:p05.fixture" const FRONT_SALES := &"P05_FIXTURE_FRONT_SALES" const BACK_SALES := &"P05_FIXTURE_BACK_SALES" const PAYER := &"P05_FIXTURE_PAYER" const SINK := &"P05_FIXTURE_SINK" var _checks := 0 var _failures := 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: print("P05-02: front and hidden funds") _test_policy_values() _test_sale_fund_routing() _test_three_business_days() _test_resume_matches_uninterrupted() _test_old_save_migration() _test_repayment_top_up() _test_purchase_top_up() _test_upkeep_top_up() await _test_ledger_opened_from_main() await process_frame if _failures == 0: print("PASS: %d checks" % _checks) else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks]) quit(_failures) func _expect(condition: bool, message: String) -> void: _checks += 1 if not condition: _failures += 1 push_error(message) func _new_fixture(seed: int) -> Dictionary: var clock: Variant = load("res://autoload/game_clock.gd").new() var state: Variant = load("res://autoload/game_state.gd").new() var runtime: Variant = load("res://autoload/simulation_runtime.gd").new() clock.reset(1, 6, 0) state.reset() _expect(bool(runtime.initialize(clock, state, seed).get(&"valid", false)), "fixture Runtime initializes") clock.set_paused(true) runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) _expect(bool(runtime.enable_production_operations().get(&"ok", false)), "fixture enables production Operations") for account: StringName in [FRONT_SALES, BACK_SALES, PAYER, SINK]: runtime.economy_service.register_account(account) runtime.economy_service.register_account(LOAN, true) # Guests' Gold enters the ledger only through explicit initialization. _expect(runtime.economy_service.initialize_account(PAYER, 100000, clock.absolute_minute, &"p05.fixture.payer").is_success(), "fixture payer funded") return {&"clock": clock, &"state": state, &"runtime": runtime} func _free_fixture(fixture: Dictionary) -> void: for key: StringName in [&"runtime", &"clock", &"state"]: if fixture.get(key) != null: fixture[key].free() # Same accelerated boundary traversal as tests/mvp/test_runtime_operations_acceptance.gd: # the fixtures hold no Facilities, Staff or logistics, only ledger postings, which # are made on 30-minute marks. func _advance(fixture: Dictionary, amount: int) -> void: var clock: Variant = fixture[&"clock"] var runtime: Variant = fixture[&"runtime"] var advanced := 0 while advanced < amount: # Every Operations boundary is on a 30-minute mark; each step ends on one. var step := mini(30 - int(clock.absolute_minute) % 30, amount - advanced) var target: int = clock.absolute_minute + step clock.absolute_minute = target clock.call("_sync_calendar_fields") clock.snapshot_version += step runtime.call("_advance_weather", target) runtime.call("_advance_facility_upkeep", target) runtime.call("_advance_operations_time", target) _align_clocks(runtime, target) advanced += step # Same as tests/mvp/test_runtime_operations_acceptance.gd: the fixture owns no # actors, cars or rides, so only the empty aggregates' clocks follow the minute. func _align_clocks(runtime: Variant, minute: int) -> void: for service: Variant in [runtime.inventory_service, runtime.facility_service, runtime.kitchen_production, runtime.guest_service, runtime.staff_service, runtime.authority_service, runtime.defense_service, runtime.construction_service, runtime.excavation_service]: service.current_minute = minute var transport_snapshot: Dictionary = runtime.elevators.create_snapshot() if not (transport_snapshot[&"shafts"] as Array).is_empty() or not (transport_snapshot[&"trips"] as Array).is_empty(): _expect(false, "accelerated fixture cannot skip live elevator movement") return transport_snapshot[&"current_minute"] = minute if not runtime.elevators.restore_snapshot(transport_snapshot): _expect(false, "empty transport clock aligns through validated snapshot restore") func _advance_to(fixture: Dictionary, minute: int) -> void: _advance(fixture, minute - int(fixture[&"clock"].absolute_minute)) func _sale(fixture: Dictionary, revenue_account: StringName, fund: StringName, amount: int, key: String) -> bool: var runtime: Variant = fixture[&"runtime"] var minute: int = fixture[&"clock"].absolute_minute var entries: Array = [ LedgerEntry.new().configure(&"", minute, revenue_account, PAYER, amount, &"SERVICE_SALE", &"p05.fixture"), LedgerEntry.new().configure(&"", minute, fund, revenue_account, amount, &"SERVICE_CASH_SWEEP", &"p05.fixture"), ] return runtime.economy_service.post(entries, StringName("p05.sale." + key)).is_success() func _move(fixture: Dictionary, from: StringName, to: StringName, amount: int, category: StringName, key: String) -> bool: var runtime: Variant = fixture[&"runtime"] return runtime.economy_service.transfer(from, to, amount, fixture[&"clock"].absolute_minute, category, &"p05.fixture", StringName("p05.move." + key)).is_success() func _fund_delta(runtime: Variant, account: StringName, after_minute: int, through_minute: int) -> int: var delta := 0 for entry: LedgerEntry in runtime.economy_service.get_entries(): if entry.absolute_minute <= after_minute or entry.absolute_minute > through_minute: continue if entry.debit_account == account: delta += entry.amount if entry.credit_account == account: delta -= entry.amount return delta func _test_policy_values() -> void: _expect(FinancePolicy.rank_tax(800) == 80 and FinancePolicy.rank_tax(109) == 10 and FinancePolicy.rank_tax(9) == 0, "A10 rank tax is 10% rounded down") _expect(FinancePolicy.rank_tax(-5) == 0, "no tax on a negative day") _expect(FinancePolicy.loan_interest(1000) == 10 and FinancePolicy.loan_interest(199) == 1 and FinancePolicy.loan_interest(99) == 0, "A11 interest is 1% per day rounded down") func _test_sale_fund_routing() -> void: var fixture := _new_fixture(5021) var runtime: Variant = fixture[&"runtime"] var back_ids: Array[StringName] = [] var front_ids: Array[StringName] = [] # Expansion definitions are read from their files; they are not registered by default. for path: String in ["res://data/facilities/adult_private_room.tres", "res://data/facilities/expansion/black_market.tres", "res://data/facilities/expansion/vip_private_salon.tres", "res://data/facilities/cafeteria.tres"]: var definition: Variant = load(path) if definition == null: continue if FinancePolicy.is_back_fund_service(definition.tags): back_ids.append(definition.id) else: front_ids.append(definition.id) _expect(back_ids.has(&"adult_private_room") and back_ids.has(&"black_market") and back_ids.has(&"vip_private_salon"), "adult, trade and private salon services earn hidden funds") _expect(front_ids.has(&"cafeteria"), "dining earns front funds") _expect(runtime.economy_service.has_account(BACK) and not runtime.economy_service.account_allows_negative(BACK), "the hidden fund is its own non-negative account") _expect(runtime.economy_service.get_balance(BACK) == 0 and runtime.economy_service.get_balance(FRONT) == 5000, "a new game starts with every Gold in front funds") _free_fixture(fixture) func _test_three_business_days() -> void: var fixture := _new_fixture(5022) var runtime: Variant = fixture[&"runtime"] var state: Variant = fixture[&"state"] var coordinator: Variant = runtime.operations_coordinator # Day 1: borrow 1000, front sale 300, hidden sale 500. _advance_to(fixture, 420) _expect(_move(fixture, LOAN, FRONT, 1000, &"LOAN_PRINCIPAL_BORROWED", "borrow"), "fixture principal borrowed") _expect(_sale(fixture, FRONT_SALES, FRONT, 300, "d1.front") and _sale(fixture, BACK_SALES, BACK, 500, "d1.back"), "day 1 sales") _advance_to(fixture, 1440) var day1: Dictionary = coordinator.get_day_summary(1) _expect(not day1.is_empty(), "day 1 closes at 00:00") var totals1: Dictionary = day1.get(&"category_totals", {}) _expect(int(totals1.get(&"revenue", -1)) == 800, "day 1 sales are front + hidden") _expect(int(totals1.get(&"taxes_and_interest", -1)) == 90, "day 1 rank tax 80 and interest 10") _expect(int(totals1.get(&"financing_principal_inflows", -1)) == 1000, "principal stays financing, outside sales") var funds1: Dictionary = day1.get(&"settlement", {}).get(&"funds", {}) _expect(int(funds1.get(&"revenue_front", -1)) == 300 and int(funds1.get(&"revenue_back", -1)) == 500, "day 1 fund report splits sales") _expect(int(funds1.get(&"rank_tax", -1)) == 80 and int(funds1.get(&"rank_tax_paid", -1)) == 80, "day 1 rank tax paid in full") _expect(int(funds1.get(&"loan_interest", -1)) == 10 and int(funds1.get(&"loan_interest_paid", -1)) == 10, "day 1 interest paid in full") _expect(int(funds1.get(&"back_top_up", -1)) == 0, "front funds alone paid day 1") _expect(runtime.economy_service.get_balance(FRONT) == 6210 and runtime.economy_service.get_balance(BACK) == 500, "day 1 balances") _expect(state.get_resource(&"gold") == runtime.economy_service.get_balance(FRONT), "Gold mirrors front funds") # The starting Gold is posted at 06:00 (minute 360); day 1 changes follow it. _expect(_fund_delta(runtime, FRONT, 360, 1440) == 1210 and _fund_delta(runtime, BACK, 360, 1440) == 500, "day 1 fund changes equal the ledger") # Day 2: front funds fall to 5, so 5 of the 10 interest comes from hidden funds. _advance_to(fixture, 1440 + 600) _expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", 6205, &"OPERATING_EXPENSE", "d2.cost"), "day 2 purchase-like cost") _advance_to(fixture, 2880) var funds2: Dictionary = coordinator.get_day_summary(2).get(&"settlement", {}).get(&"funds", {}) _expect(int(funds2.get(&"back_top_up", -1)) == 5, "A09: hidden funds cover the 5 front funds cannot pay") _expect(int(funds2.get(&"loan_interest_paid", -1)) == 10 and int(funds2.get(&"rank_tax", -1)) == 0, "day 2 interest paid, no sales no tax") _expect(runtime.economy_service.get_balance(FRONT) == 0 and runtime.economy_service.get_balance(BACK) == 495, "day 2 balances") _expect(int(coordinator.get_day_summary(2)[&"category_totals"][&"revenue"]) == 0, "the top-up is not sales") _expect(_fund_delta(runtime, FRONT, 1440, 2880) == -6210 and _fund_delta(runtime, BACK, 1440, 2880) == -5, "day 2 fund changes equal the ledger") # Day 3: hidden funds are gone too, so the interest stays unpaid. _advance_to(fixture, 2880 + 600) _expect(_move(fixture, BACK, SINK, 495, &"TRANSFER", "d3.drain"), "day 3 hidden funds drained") _advance_to(fixture, 4320) var day3: Dictionary = coordinator.get_day_summary(3) var funds3: Dictionary = day3.get(&"settlement", {}).get(&"funds", {}) _expect(int(funds3.get(&"loan_interest", -1)) == 10 and int(funds3.get(&"loan_interest_paid", -1)) == 0 and int(funds3.get(&"loan_interest_arrears", -1)) == 10, "A09: unpaid interest carries over") _expect(int(day3[&"settlement"][&"arrears_total"]) == 10, "day 3 arrears include the interest") _expect(int(runtime.get_operations_report().get(&"arrears_total", -1)) == 10, "Operations report sees the interest arrears") _expect(int(day3[&"category_totals"][&"taxes_and_interest"]) == 10, "the unpaid interest is still the day's expense") # Day 4: a front sale pays the older interest first. _advance_to(fixture, 4320 + 600) _expect(_sale(fixture, FRONT_SALES, FRONT, 100, "d4.front"), "day 4 sale") _advance_to(fixture, 5760) var funds4: Dictionary = coordinator.get_day_summary(4).get(&"settlement", {}).get(&"funds", {}) _expect(int(funds4.get(&"rank_tax", -1)) == 10 and int(funds4.get(&"rank_tax_paid", -1)) == 10, "day 4 tax paid") _expect(int(funds4.get(&"loan_interest_paid", -1)) == 20 and int(funds4.get(&"loan_interest_arrears", -1)) == 0, "day 4 pays the older interest and the new one") _expect(runtime.economy_service.get_balance(FRONT) == 70, "day 4 front balance") _expect(runtime.economy_service.validate_ledger()[&"ok"], "the ledger reconciles after four closes") _check_ledger_panel(runtime, fixture[&"clock"]) var saved: Dictionary = runtime.create_snapshot() _expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "four closed days restore exactly") _free_fixture(fixture) func _scripted_days(fixture: Dictionary, restore_at: Array[int], through_files := false) -> Dictionary: var runtime: Variant = fixture[&"runtime"] # Postings and save/restore points in time order; the last minute closes day 3. var steps: Array = [ [420, func() -> void: _move(fixture, LOAN, FRONT, 1000, &"LOAN_PRINCIPAL_BORROWED", "borrow")], [450, func() -> void: _sale(fixture, BACK_SALES, BACK, 700, "d1.back")], [1440, Callable()], [2040, func() -> void: _move(fixture, FRONT, &"OPERATING_EXPENSE", 5900, &"OPERATING_EXPENSE", "d2.cost")], [2400, Callable()], [2880, Callable()], [3510, func() -> void: _sale(fixture, FRONT_SALES, FRONT, 50, "d3.front")], [4320, Callable()], ] for step: Array in steps: var minute := int(step[0]) _advance_to(fixture, minute) if (step[1] as Callable).is_valid(): (step[1] as Callable).call() if minute in restore_at and through_files: # The ordinary save file path: write the save, then load it back. var path := "user://p05_resume_%d.save" % minute _expect(bool(runtime.save_to_path(path).get(&"ok", false)), "save file at minute %d" % minute) _expect(bool(runtime.load_from_path(path).get(&"ok", false)), "load the save file at minute %d" % minute) elif minute in restore_at: var saved: Dictionary = runtime.create_snapshot() _expect(runtime.restore_snapshot(saved), "restore at minute %d" % minute) return runtime.create_snapshot() func _test_resume_matches_uninterrupted() -> void: var straight := _new_fixture(5023) var resumed := _new_fixture(5023) var a := _scripted_days(straight, []) var b := _scripted_days(resumed, [1440, 2400, 2880]) _expect(var_to_bytes(a) == var_to_bytes(b), "saving mid-day and at 00:00 changes nothing: no double tax, interest or top-up") var runtime: Variant = resumed[&"runtime"] var keys := 0 for entry: LedgerEntry in runtime.economy_service.get_entries(): if entry.category in [FinancePolicy.RANK_TAX_CATEGORY, FinancePolicy.LOAN_INTEREST_CATEGORY]: keys += 1 # Day 1: tax 70 and interest 10; day 2: interest only (no sales); day 3: tax 5 and interest 10. _expect(keys == 5, "each tax and interest accrual is posted once (no duplicates)") # Through save files the save and load events are new, so compare the funds side only. var through_files := _new_fixture(5023) var c := _scripted_days(through_files, [1440, 2400, 2880], true) _expect(var_to_bytes(c[&"systems"][&"runtime"][&"economy"]) == var_to_bytes(a[&"systems"][&"runtime"][&"economy"]), "saving to files and loading them back leaves the ledger exactly as without saving") var straight_days: Array = straight[&"runtime"].operations_coordinator.get_day_summaries() var file_days: Array = through_files[&"runtime"].operations_coordinator.get_day_summaries() # Operations history is persisted in a transport-safe form (StringName ids become # String values), so compare as the Runtime's own restore crosslink does. var runtime_a: Variant = straight[&"runtime"] _expect(runtime_a.call("_strict_variants_equal", runtime_a.call("_operations_snapshot_comparison_value", straight_days), runtime_a.call("_operations_snapshot_comparison_value", file_days)), "the daily closes, fund reports and arrears are the same through save files") _free_fixture(straight) _free_fixture(resumed) _free_fixture(through_files) func _test_old_save_migration() -> void: var fixture := _new_fixture(5024) var runtime: Variant = fixture[&"runtime"] _advance_to(fixture, 420) var current: Dictionary = runtime.create_snapshot() var old := current.duplicate(true) var economy: Dictionary = old[&"systems"][&"runtime"][&"economy"] var kept: Array = [] for account: Dictionary in economy[&"accounts"]: if String(account[&"account_id"]) not in ["BACK_CASH", "RANK_TAX_EXPENSE", "RANK_TAX_PAYABLE", "LOAN_INTEREST_EXPENSE", "LOAN_INTEREST_PAYABLE"]: kept.append(account) economy[&"accounts"] = kept _expect(runtime.restore_snapshot(old), "A12: a save from before the split loads") _expect(runtime.economy_service.get_balance(FRONT) == 5000 and runtime.economy_service.get_balance(BACK) == 0, "A12: every Gold is front funds, hidden funds start at 0") _expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(current), "A12: the migrated save equals a current save of the same game") var partial := old.duplicate(true) var partial_accounts: Array = partial[&"systems"][&"runtime"][&"economy"][&"accounts"] partial_accounts.append({&"account_id": "RANK_TAX_PAYABLE", &"allow_negative": true, &"system": false}) var before: Dictionary = runtime.create_snapshot() _expect(not runtime.restore_snapshot(partial) and var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "a partial set of fund accounts is rejected and changes nothing") _free_fixture(fixture) func _test_repayment_top_up() -> void: var fixture := _new_fixture(5025) var runtime: Variant = fixture[&"runtime"] _advance_to(fixture, 420) _expect(_move(fixture, LOAN, FRONT, 1000, &"LOAN_PRINCIPAL_BORROWED", "borrow"), "fixture principal") _expect(_sale(fixture, BACK_SALES, BACK, 300, "back") and _move(fixture, FRONT, &"OPERATING_EXPENSE", 5980, &"OPERATING_EXPENSE", "cost"), "front 20, hidden 300") var receipts: Array = [] runtime.economy_service.transaction_posted.connect(func(id: StringName, _entries: Array) -> void: receipts.append(id)) var before: Dictionary = runtime.create_snapshot() _expect(not runtime.repay_loan_principal(&"p05.fixture", 400, &"p05.repay.too_much").is_success() and var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "front + hidden short: repayment rejected, nothing changes") var paid: CommandResult = runtime.repay_loan_principal(&"p05.fixture", 200, &"p05.repay") _expect(paid.is_success() and int(paid.details.get(&"outstanding_principal", -1)) == 800, "repayment uses hidden funds for the rest") _expect(runtime.economy_service.get_balance(FRONT) == 0 and runtime.economy_service.get_balance(BACK) == 120, "front 20 first, then 180 from hidden funds") _expect(receipts.size() == 1, "payment and top-up are one transaction") var entries: Array = runtime.economy_service.get_transaction_entries(StringName(paid.details[&"transaction_id"])) _expect(entries.size() == 2 and entries[0].category == FinancePolicy.TOP_UP_CATEGORY and entries[1].category == &"LOAN_PRINCIPAL_REPAID", "top-up then principal") _expect(fixture[&"state"].get_resource(&"gold") == 0, "Gold mirrors front funds after repayment") _free_fixture(fixture) func _test_purchase_top_up() -> void: var clock: Variant = load("res://autoload/game_clock.gd").new() var state: Variant = load("res://autoload/game_state.gd").new() var runtime: Variant = load("res://autoload/simulation_runtime.gd").new() clock.reset(1, 6, 0) state.reset() _expect(bool(runtime.initialize(clock, state, 5026).get(&"valid", false)), "purchase fixture Runtime initializes") clock.set_paused(true) runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) for row: Array in [[&"warehouse", Vector3i(-8, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]: _expect(runtime.submit_command(&"build_facility", {&"definition_id": row[0], &"origin": row[1]}).is_success(), "build %s" % row[0]) clock.advance_minutes(180) var warehouse := &"" for facility: FacilityInstance in runtime.facility_service.get_instances(): if facility.definition_id == &"warehouse": warehouse = facility.instance_id _expect(not warehouse.is_empty(), "warehouse completes") var fixture := {&"clock": clock, &"state": state, &"runtime": runtime} for account: StringName in [BACK_SALES, PAYER]: runtime.economy_service.register_account(account) _expect(runtime.economy_service.initialize_account(PAYER, 1000, clock.absolute_minute, &"p05.purchase.payer").is_success(), "fixture payer") var front: int = runtime.economy_service.get_balance(FRONT) _expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", front - 2, &"OPERATING_EXPENSE", "purchase.drain") and _sale(fixture, BACK_SALES, BACK, 100, "purchase.back"), "front 2, hidden 100") var preview: Dictionary = runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5) _expect(bool(preview.get(&"ok", false)) and int(preview[&"total_cost"]) == 15 and int(preview[&"cash_after"]) == 87, "A09: the quote counts front + hidden funds") var paid: CommandResult = runtime.submit_command(&"purchase_supplies", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 5}) _expect(paid.is_success(), "the purchase uses hidden funds for what front funds cannot pay") _expect(runtime.economy_service.get_balance(FRONT) == 0 and runtime.economy_service.get_balance(BACK) == 87, "front 2 first, then 13 from hidden funds") var purchase_entries: Array = runtime.economy_service.get_transaction_entries(StringName(paid.details.get(&"transaction_id", &""))) _expect(purchase_entries.size() == 1 and purchase_entries[0].credit_account == FRONT and purchase_entries[0].amount == 15, "the purchase receipt keeps its single front-fund entry") var top_ups := 0 for entry: LedgerEntry in runtime.economy_service.get_entries(): if entry.category == FinancePolicy.TOP_UP_CATEGORY: top_ups += entry.amount _expect(top_ups == 13, "one 13 Gold top-up from hidden funds") _expect(state.get_resource(&"gold") == 0, "Gold mirrors front funds after the purchase") # A refused command is still recorded as processed; the funds and stock must not move. var economy_before: Dictionary = runtime.economy_service.create_snapshot() var inventory_before: Dictionary = runtime.inventory_service.create_snapshot() var refused: CommandResult = runtime.submit_command(&"purchase_supplies", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 40}) _expect(not refused.is_success() and StringName(refused.details.get(&"reason", &"")) == &"INSUFFICIENT_FUNDS", "front + hidden short: purchase refused for funds") _expect(var_to_bytes(economy_before) == var_to_bytes(runtime.economy_service.create_snapshot()) \ and var_to_bytes(inventory_before) == var_to_bytes(runtime.inventory_service.create_snapshot()), "the refused purchase moves no Gold and no stock") var saved: Dictionary = runtime.create_snapshot() _expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "purchase with a top-up restores exactly") runtime.free() clock.free() state.free() func _test_upkeep_top_up() -> void: var fixture := _new_fixture(5027) var runtime: Variant = fixture[&"runtime"] var clock: Variant = fixture[&"clock"] _expect(runtime.submit_command(&"build_facility", {&"definition_id": &"warehouse", &"origin": Vector3i(-8, 0, 1)}).is_success(), "build a warehouse") clock.advance_minutes(180) _advance_to(fixture, 600) var front: int = runtime.economy_service.get_balance(FRONT) _expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", front, &"OPERATING_EXPENSE", "upkeep.drain") and _sale(fixture, BACK_SALES, BACK, 1000, "upkeep.back"), "front 0, hidden 1000") _advance_to(fixture, 1440) var summary: Dictionary = runtime.operations_coordinator.get_day_summary(1) var maintenance := int(summary.get(&"category_totals", {}).get(&"maintenance", -1)) var funds: Dictionary = summary.get(&"settlement", {}).get(&"funds", {}) _expect(maintenance > 0, "the warehouse owes upkeep for day 1") _expect(int(funds.get(&"back_top_up", -1)) == maintenance + int(funds.get(&"rank_tax", 0)), "A09: hidden funds cover the upkeep (and tax) front funds cannot pay") _expect(runtime.upkeep_settlement.get_arrears_total() == 0 and int(summary[&"settlement"][&"arrears_total"]) == 0, "no upkeep arrears") _expect(runtime.economy_service.get_balance(FRONT) == 0 and runtime.economy_service.get_balance(BACK) == 1000 - int(funds[&"back_top_up"]), "front 0, hidden paid the rest") _free_fixture(fixture) # F7: the ordinary ledger panel reads both funds, the rank tax, the interest, the # top-ups and their arrears from the same ledger and daily closes. func _check_ledger_panel(runtime: Variant, clock: Variant) -> void: var panel := OperatingLedgerPanel.new() root.add_child(panel) panel.setup(runtime, clock) var report: Dictionary = panel.get_report() _expect(int(report.get(&"cash", -1)) == runtime.economy_service.get_balance(FRONT) and int(report.get(&"back_cash", -1)) == runtime.economy_service.get_balance(BACK), "the panel reports both funds") _expect(int(report.get(&"finance_arrears", -1)) == 0, "the panel reports no tax or interest arrears after day 4") var cash_text: String = panel._cash_label.text _expect(cash_text.contains("裏金 %dG" % runtime.economy_service.get_balance(BACK)) and cash_text.contains("本日の税") and cash_text.contains("利息"), "the cash section shows the hidden fund, tax and interest") panel.select_latest_close() var close_text: String = panel._closed_label.text _expect(close_text.contains("うち表の売上 100G / 裏の売上 0G") and close_text.contains("世界樹ランク税 10G(支払 10G)") \ and close_text.contains("利息 10G(支払 20G)") and close_text.contains("未払い 税 0G・利息 0G"), "the day 4 close shows sales by fund, tax, interest and arrears") panel.queue_free() # F7 through ordinary operation: after a real 00:00 close on the game's own # Runtime and clock, Main's 帳簿 navigation button opens the ledger. func _test_ledger_opened_from_main() -> void: var runtime: Variant = root.get_node("SimulationRuntime") var clock: Variant = root.get_node("GameClock") var state: Variant = root.get_node("GameState") clock.reset(1, 6, 0) state.reset() runtime.initialize(clock, state, 5028) clock.set_paused(true) root.size = Vector2i(1440, 900) var main: Control = load("res://scenes/main/main.tscn").instantiate() root.add_child(main) await process_frame runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) for account: StringName in [FRONT_SALES, BACK_SALES, PAYER]: runtime.economy_service.register_account(account) runtime.economy_service.register_account(LOAN, true) _expect(runtime.economy_service.initialize_account(PAYER, 100000, clock.absolute_minute, &"p05.fixture.payer").is_success(), "game Runtime fixture payer funded") clock.advance_minutes(60) var fixture := {&"clock": clock, &"state": state, &"runtime": runtime} _expect(_move(fixture, LOAN, FRONT, 1000, &"LOAN_PRINCIPAL_BORROWED", "ui.borrow") and _sale(fixture, FRONT_SALES, FRONT, 300, "ui.front") and _sale(fixture, BACK_SALES, BACK, 500, "ui.back"), "day 1 loan and sales on the game Runtime") clock.advance_minutes(1440 - int(clock.absolute_minute)) _expect(not runtime.operations_coordinator.get_day_summary(1).is_empty(), "the normal clock closes day 1 at 00:00") var ledger_button: Button = (main.get("navigation_buttons") as Dictionary)[&"ledger"] ledger_button.pressed.emit() var panel: OperatingLedgerPanel = main.get("operating_ledger_panel") _expect(panel.visible and ledger_button.button_pressed, "the 帳簿 button opens the ledger") var report: Dictionary = panel.get_report() _expect(int(report.get(&"cash", -1)) == 6210 and int(report.get(&"back_cash", -1)) == 500, "the opened ledger reports front 6210 and hidden 500") # The day 1 tax and interest are paid at the 00:00 that opens day 2. var cash_text: String = panel._cash_label.text _expect(cash_text.contains("裏金 500G(本日の入金 +0G / 表金へ補填 −0G)") and cash_text.contains("本日の税 80G / 利息 10G(未払い 0G)"), "the opened ledger shows the hidden fund, today's tax and interest") var close_text: String = panel._closed_label.text _expect(close_text.contains("うち表の売上 300G / 裏の売上 500G") and close_text.contains("世界樹ランク税 80G(支払 80G)/ 利息 10G(支払 10G)") and close_text.contains("裏金から表金へ補填 0G / 未払い 税 0G・利息 0G"), "the opened ledger shows the day 1 close by fund with tax and interest") var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main) _expect(bool(cleanup.get(&"passed", false)), "ordinary Main release with audio cleanup")