@@ -0,0 +1,462 @@
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 ( " 裏金 %d G " % 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 " )