Files
TheTree_mock/tests/mvp/test_original_midnight_settlement.gd
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

264 lines
22 KiB
GDScript

extends SceneTree
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var service := OperationsCoordinator.new({&"start_minute": 360})
_bind(service, [-1, 0, -1, 1, -1, -1, -1], 0)
_expect(service.advance_to(1439).is_success() and service.get_day_summaries().is_empty(), "23:59 remains open")
var before_close := service.create_snapshot()
var boundary_restored := OperationsCoordinator.new()
_expect(boundary_restored.restore_snapshot(before_close), "23:59 save restores before the first close")
_bind(boundary_restored, [-1, 0, -1, 1, -1, -1, -1], 0)
boundary_restored.advance_to(1440)
_expect(service.advance_to(1440).is_success(), "first partial opening day closes at midnight")
_expect(boundary_restored.deterministic_digest() == service.deterministic_digest(), "save immediately before midnight closes and pays exactly once")
var legacy_open := before_close.duplicate(true)
legacy_open["schema_version"] = 3
legacy_open.erase("legacy_calendar_last_day")
legacy_open.erase("legacy_history_last_day")
legacy_open.erase("legacy_principal_profit_last_day")
var legacy_restored := OperationsCoordinator.new()
_expect(legacy_restored.restore_snapshot(legacy_open), "legacy save before its first close restores")
legacy_restored.advance_to(1500)
_expect(legacy_restored.get_day_summaries().is_empty(), "unclosed legacy first day is not retroactively settled at midnight")
legacy_restored.advance_to(1800)
_expect(legacy_restored.get_day_summary(1)[&"closed_at"] == 1800, "legacy first open day closes once at 06:00")
var day := service.get_day_summary(1)
_expect(day[&"closed_at"] == 1440 and day[&"period_start_minute"] == 0, "first day uses calendar midnight bounds")
_expect(service.get_consecutive_insolvency_count() == 1 and service.get_arrears_total() == 0, "a paid loss counts even without arrears")
service.advance_to(1800)
_expect(service.get_day_summaries().size() == 1 and not service.get_morning_report(2).is_empty(), "06:00 performs morning work without another close")
service.advance_to(2880)
_expect(service.get_consecutive_insolvency_count() == 0, "break-even resets loss streak")
service.advance_to(4320)
service.advance_to(5760)
_expect(service.get_consecutive_insolvency_count() == 0, "profit resets loss streak")
service.advance_to(8640)
var saved := service.create_snapshot()
var restored := OperationsCoordinator.new()
_expect(restored.restore_snapshot(saved), "two-loss snapshot restores")
var missing_alert := saved.duplicate(true)
missing_alert["alerts"].pop_back()
_expect(not restored.restore_snapshot(missing_alert) and restored.create_snapshot() == saved, "missing loss warning rejects atomically")
_bind(restored, [-1, 0, -1, 1, -1, -1, -1], 0)
service.advance_to(10200)
restored.advance_to(10200)
_expect(service.current_minute == 10080 and service.is_terminal(), "third loss stops a larger advance exactly at midnight")
_expect(service.deterministic_digest() == restored.deterministic_digest(), "save/load continuation preserves exact terminal outcome")
_expect(service.validate_invariants()[&"ok"], "loss history and terminal invariants reconcile")
var unpaid := OperationsCoordinator.new()
_bind(unpaid, [1, 1, 1, 1], 50)
unpaid.advance_to(5760)
_expect(not unpaid.is_terminal() and unpaid.get_consecutive_insolvency_count() == 0, "profitable closes with arrears never count as losses")
_expect(not unpaid.can_start_construction_or_investment()[&"allowed"], "unpaid wages still restrict new investment separately")
_test_legacy()
_test_principal_financing()
_test_principal_repayment()
_test_principal_command()
_test_principal_profit_marker()
print("%s: %d checks" % ["PASS" if _failures == 0 else "FAIL", _checks])
quit(_failures)
func _test_legacy() -> void:
var old: Dictionary = str_to_var(FileAccess.get_file_as_string("res://tests/mvp/fixtures/operations_v3_0600.txt"))
var service := OperationsCoordinator.new()
_expect(service.restore_snapshot(old), "genuine pre-change v3 financial history restores")
_expect(service.get_day_summaries() == old["day_summaries"], "legacy closed financial records remain unchanged")
_expect(service.get_consecutive_insolvency_count() == 2, "historical unpaid streak remains auditable")
_bind(service, [-1, -1, -1, -1, -1], 0)
service.advance_to(4320)
_expect(service.get_day_summaries().size() == 2, "migration does not duplicate the still-open legacy day at midnight")
service.advance_to(4680)
_expect(service.get_day_summary(3)[&"period_start_minute"] == 3240 and service.get_consecutive_insolvency_count() == 1, "open legacy day closes once; new loss streak excludes old arrears count")
var snapshot := service.create_snapshot()
var restored := OperationsCoordinator.new()
_expect(restored.restore_snapshot(snapshot), "transition save restores")
_bind(restored, [-1, -1, -1, -1, -1], 0)
service.advance_to(5760)
restored.advance_to(5760)
_expect(service.get_day_summary(4)[&"period_start_minute"] == 4680 and service.get_day_summary(4)[&"closed_at"] == 5760, "18-hour transition has no gap or overlap")
_expect(service.deterministic_digest() == restored.deterministic_digest(), "migration replay is deterministic")
var before := restored.create_snapshot()
var bad := before.duplicate(true)
bad["legacy_calendar_last_day"] = 1
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == before, "inconsistent transition marker rejects atomically")
service.advance_to(8000)
_expect(service.current_minute == 7200 and service.is_terminal() and service.validate_invariants()[&"ok"], "migrated save ends on three new losses at midnight")
func _bind(service: OperationsCoordinator, profits: Array, arrears: int) -> void:
service.configure_callbacks(func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase != &"apply": return {&"ok": true, &"token": token}
var profit := int(profits[int(payload[&"day"]) - 1])
return {&"ok": true, &"category_totals": {&"revenue": 100 + profit, &"wages": 100}, &"facility_profit_loss": {}, &"anomalies": [], &"reconciliation_difference": 0, &"settlement": {&"arrears_total": arrears}})
func _expect(condition: bool, message: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(message)
func _test_principal_financing() -> void:
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset()
_expect(runtime.initialize(clock,state,1131009)[&"valid"],"principal fixture Runtime initializes")
clock.set_paused(true)
var before: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(before) and var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()),"no-loan current snapshot retains exact whole representation")
var coordinator := OperationsCoordinator.new({&"start_minute":360})
var loan := &"LOAN_LIABILITY:principal.fixture"
_expect(runtime.economy_service.register_account(loan,true),"explicit fixture liability account")
_expect(runtime.economy_service.transfer(loan,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",&"principal.fixture",&"principal.borrow").is_success(),"real principal borrowed")
_expect(runtime.economy_service.transfer(&"PLAYER_CASH",loan,400,clock.absolute_minute,&"LOAN_PRINCIPAL_REPAID",&"principal.fixture",&"principal.repay").is_success(),"real principal repayment")
var report: Dictionary = runtime.call("_build_operations_daily_financials",360,361,0,[])
_expect(report.get(&"ok",false),"valid principal ledger produces daily report")
if report.get(&"ok",false):
var totals: Dictionary = report[&"category_totals"]
_expect(totals[&"revenue"]==0 and totals[&"cost_of_goods"]==0,"principal never enters operating sales or costs")
_expect(totals[&"financing_principal_inflows"]==1000 and totals[&"financing_principal_outflows"]==400,"gross principal cash flows separately classified")
_expect(report[&"facility_profit_loss"].is_empty(),"principal never allocated to facility profit")
var normal: Dictionary = coordinator.call("_normalize_categories",totals)
_expect(normal.get(&"ok",false) and normal[&"categories"]==totals,"Operations and Runtime retain identical financing categories")
_expect(coordinator.call("_net_profit",normal[&"categories"])==-400,"original debt repayment counts as daily expense while borrowing remains outside sales")
var expected: Variant = runtime.call("_operations_expected_category_totals",runtime.economy_service,360,361,0)
_expect(expected==totals,"restore recomputes same full category totals")
coordinator.configure_callbacks(func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase != &"apply":return {&"ok":true,&"token":token}
var close: Dictionary = runtime.call("_build_operations_daily_financials",int(payload[&"period_start_minute"]),int(payload[&"period_end_minute_exclusive"]),0,[])
close[&"settlement"]={&"arrears_total":0}
return close)
_expect(coordinator.advance_to(4320).is_success(),"three calendar days close with actual principal ledger")
_expect(coordinator.get_day_summaries().size()==3,"three actual daily financing reports retained")
var first: Dictionary = coordinator.get_day_summary(1)
_expect(first[&"category_totals"][&"financing_principal_inflows"]==1000 and first[&"category_totals"][&"financing_principal_outflows"]==400 and first[&"net_profit"]==-400,"actual repayment reduces daily profit at the real day boundary")
_expect(not coordinator.get_day_summary(2)[&"category_totals"].has(&"financing_principal_inflows") and coordinator.get_day_summary(2)[&"net_profit"]==0,"no-loan following day preserves old shape and profit")
var days: Dictionary = coordinator.create_snapshot()
var restored_days := OperationsCoordinator.new()
_expect(restored_days.restore_snapshot(days) and var_to_bytes(restored_days.create_snapshot())==var_to_bytes(days),"whole three-day financing history restores byte-exact")
var saved: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"whole principal snapshot restores exactly")
var duplicate: CommandResult = runtime.economy_service.transfer(loan,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",&"principal.fixture",&"principal.borrow")
_expect(not duplicate.is_success() and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"principal duplicate after restore remains whole state noop")
var old_categories := {&"revenue":0,&"refunds":0,&"cost_of_goods":0,&"wages":0,&"maintenance":0,&"utilities":0,&"taxes_and_interest":0,&"fines":0,&"construction_cash_flow":0}
var legacy: Dictionary = coordinator.call("_normalize_categories",old_categories)
_expect(legacy[&"ok"] and var_to_bytes(legacy[&"categories"])==var_to_bytes(old_categories),"old categories retain exact shape without invented financing zeros")
var bad := old_categories.duplicate(true);bad[&"mystery_financing"]=1
_expect(not coordinator.call("_normalize_categories",bad)[&"ok"],"unclassified finance categories still reject")
for value: Variant in [-1,"1000",OperationsCoordinator.MAX_FINANCIAL_AMOUNT+1]:
bad=old_categories.duplicate(true);bad[&"financing_principal_inflows"]=value
_expect(not coordinator.call("_normalize_categories",bad)[&"ok"],"invalid principal total rejects")
var corrupt := EconomyService.new()
_expect(corrupt.restore_snapshot(runtime.economy_service.create_snapshot()),"corruption fixture actual full economy")
_expect(corrupt.transfer(loan,&"PLAYER_CASH",1,clock.absolute_minute,&"TRANSFER",&"principal.fixture",&"principal.unattested").is_success(),"explicit unattributed liability movement fixture")
var altered := saved.duplicate(true)
altered[&"systems"][&"runtime"][&"economy"]=corrupt.create_snapshot()
altered[&"game_state"][&"resources"][&"gold"]=5601
_expect(not runtime.restore_snapshot(altered) and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"unattested liability movement rejects whole Runtime restore atomically")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5600 and runtime.economy_service.get_balance(loan)==-600,"exact remaining principal and cash")
runtime.free();clock.free();state.free()
func _test_principal_repayment() -> void:
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset()
_expect(runtime.initialize(clock,state,1161009)[&"valid"],"repayment Runtime initializes")
clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var loan := &"LOAN_LIABILITY:repayment.fixture"
_expect(runtime.economy_service.register_account(loan,true),"explicit existing fixture liability")
_expect(runtime.economy_service.transfer(loan,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",&"repayment.fixture",&"repayment.borrow").is_success(),"fixture principal borrowed in real ledger")
var receipts: Array[Dictionary]=[]
runtime.economy_service.transaction_posted.connect(func(id: StringName,_entries: Array) -> void:
receipts.append({&"id":id,&"cash":runtime.economy_service.get_balance(&"PLAYER_CASH"),&"gold":state.get_resource(&"gold")}))
for amount: Variant in [0,-1,1.5,"400",1001,OperationsCoordinator.MAX_FINANCIAL_AMOUNT+1]:
var before: Dictionary=runtime.create_snapshot()
_expect(not runtime.repay_loan_principal(&"repayment.fixture",amount,StringName("bad."+str(_checks))).is_success(),"invalid or excessive principal payment rejects")
_expect(var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()) and receipts.is_empty(),"rejected principal preserves whole state and notifications")
var result: CommandResult=runtime.repay_loan_principal(&"repayment.fixture",400,&"repayment.first")
_expect(result.is_success() and result.details[&"paid_principal"]==400 and result.details[&"outstanding_principal"]==600,"public partial repayment reports exact remaining principal")
_expect(receipts.size()==1 and receipts[0][&"cash"]==5600 and receipts[0][&"gold"]==5600,"receipt sees committed principal and synchronized Gold")
var saved: Dictionary=runtime.create_snapshot()
_expect(not runtime.repay_loan_principal(&"repayment.fixture",400,&"repayment.first").is_success() and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()) and receipts.size()==1,"principal replay is whole-state and receipt noop")
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"whole paid principal snapshot restores exact")
_expect(not runtime.repay_loan_principal(&"repayment.fixture",400,&"repayment.first").is_success() and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"restored payment key cannot charge twice")
runtime.economy_service.begin_notification_scope()
_expect(runtime.repay_loan_principal(&"repayment.fixture",600,&"repayment.final").is_success() and state.get_resource(&"gold")==5000,"nested final payment commits whole principal and Gold before receipt")
runtime.economy_service.end_notification_scope()
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5000 and runtime.economy_service.get_balance(loan)==0,"public final repayment extinguishes only the actual principal")
var completed: Dictionary=runtime.create_snapshot()
_expect(not runtime.repay_loan_principal(&"repayment.fixture",1,&"repayment.extra").is_success() and var_to_bytes(completed)==var_to_bytes(runtime.create_snapshot()),"extinguished principal cannot be overpaid")
var report: Dictionary=runtime.call("_build_operations_daily_financials",360,361,0,[])
_expect(report[&"ok"] and report[&"category_totals"][&"financing_principal_inflows"]==1000 and report[&"category_totals"][&"financing_principal_outflows"]==1000 and report[&"category_totals"][&"revenue"]==0 and report[&"category_totals"][&"cost_of_goods"]==0,"public repayment stays financing in actual daily report")
runtime.free();clock.free();state.free()
func _test_principal_command() -> void:
var runtime: Variant=load("res://autoload/simulation_runtime.gd").new()
var clock: Variant=load("res://autoload/game_clock.gd").new()
var state: Variant=load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset()
_expect(runtime.initialize(clock,state,1191009)[&"valid"],"principal command Runtime initializes")
clock.set_paused(true);runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var id:=&"principal.command.fixture";var account:=&"LOAN_LIABILITY:principal.command.fixture"
_expect(runtime.economy_service.register_account(account,true),"explicit command fixture liability")
_expect(runtime.economy_service.transfer(account,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",id,&"command.fixture.borrow").is_success(),"actual command fixture debt")
var result: CommandResult=runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":400},&"player",id,0,-1,&"command.fixture.first")
_expect(result.is_success() and result.command_id>0 and result.target_id==id and result.details[&"outstanding_principal"]==600,"ordinary paused command reports actual payment and target")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5600 and runtime.economy_service.get_balance(account)==-600 and state.get_resource(&"gold")==5600,"ordinary command commits exact principal and Gold")
var bad_payloads: Array[Dictionary]=[{},{&"loan_id":123,&"amount":1},{&"loan_id":id,&"amount":1.5},{&"loan_id":id,&"amount":601},{&"loan_id":id,&"amount":1}]
for i in range(bad_payloads.size()):
var before: Dictionary=runtime.economy_service.create_snapshot();var gold: int=state.get_resource(&"gold")
var key:=StringName("command.bad."+str(i)) if i<4 else &""
result=runtime.submit_command(&"repay_loan_principal",bad_payloads[i],&"player",id,0,-1,key)
_expect(not result.is_success(),"invalid principal command rejects without permissive conversion")
_expect(var_to_bytes(before)==var_to_bytes(runtime.economy_service.create_snapshot()) and state.get_resource(&"gold")==gold,"failed command keeps exact financial state")
var before: Dictionary=runtime.create_snapshot()
_expect(runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":400},&"player",id,0,-1,&"command.fixture.first").code==CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY,"ordinary processed command key cannot repay twice")
_expect(var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()),"duplicate command preserves entire Runtime")
var due: int=clock.absolute_minute+2
_expect(runtime.submit_command(&"repay_loan_principal",{"loan_id":String(id),"amount":200},&"player",id,0,due,&"command.fixture.future").is_success(),"future principal command accepts serialized string payload")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5600,"future command has no early payment")
var pending: Dictionary=runtime.create_snapshot();clock.advance_minutes(2)
_expect(runtime.command_queue.get_result_by_idempotency_key(&"command.fixture.future").is_success() and runtime.economy_service.get_balance(account)==-400,"future command pays once through clock tick")
var tx: StringName=runtime.economy_service.get_transaction_id_for_idempotency_key(&"command.fixture.future")
var entries: Array=runtime.economy_service.get_transaction_entries(tx)
_expect(entries.size()==1 and entries[0].absolute_minute==due,"principal ledger uses actual future execution minute")
var continued: Dictionary=runtime.create_snapshot()
_expect(runtime.restore_snapshot(pending) and var_to_bytes(pending)==var_to_bytes(runtime.create_snapshot()),"pending principal command restores exact")
clock.advance_minutes(2)
_expect(var_to_bytes(continued)==var_to_bytes(runtime.create_snapshot()),"pending principal command continuation equals original whole state")
before=runtime.create_snapshot()
_expect(runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":200},&"player",id,0,-1,&"command.fixture.future").code==CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY,"restored command key rejects repeat")
_expect(var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()),"restored duplicate preserves entire Runtime")
_expect(runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":400},&"player",id,0,-1,&"command.fixture.final").is_success(),"queued remaining principal fully repays")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5000 and runtime.economy_service.get_balance(account)==0 and state.get_resource(&"gold")==5000,"queued final payment conserves exact initial cash")
runtime.free();clock.free();state.free()
func _test_principal_profit_marker() -> void:
var service := OperationsCoordinator.new()
service.configure_callbacks(func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase != &"apply": return {&"ok": true, &"token": token}
return {&"ok": true, &"category_totals": {&"financing_principal_outflows": 500}, &"facility_profit_loss": {}, &"anomalies": [], &"reconciliation_difference": 0, &"settlement": {&"arrears_total": 0}})
_expect(service.advance_to(5000).is_success(), "principal expense closes through terminal boundary")
_expect(service.current_minute == 4320 and service.is_terminal(), "three principal expenses stop at third midnight")
for day in range(1, 4):
var summary := service.get_day_summary(day)
_expect(summary[&"net_profit"] == -500 and summary[&"consecutive_insolvency_count"] == day, "principal expense determines original daily profit and loss streak")
var saved := service.create_snapshot()
_expect(saved["schema_version"] == 5 and saved["legacy_principal_profit_last_day"] == 0, "new history uses explicit original profit basis")
var restored := OperationsCoordinator.new()
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "current original-profit terminal history restores exactly")
for value: Variant in [-1, 4, 0.5, "0", null]:
var bad := saved.duplicate(true)
bad["legacy_principal_profit_last_day"] = value
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == saved, "invalid principal history marker rejects atomically")
var missing := saved.duplicate(true)
missing.erase("legacy_principal_profit_last_day")
_expect(not restored.restore_snapshot(missing) and restored.create_snapshot() == saved, "missing current marker rejects atomically")