extends RefCounted ## P05-02 F6: the fixture and day script shared by the restart writer ## (tests/economy/test_p05_funds_restart_1_write.gd) and reader ## (tests/economy/test_p05_funds_restart_2_read.gd), which run as separate engine ## processes. Same ledger fixture and accelerated boundary traversal as ## tests/economy/test_p05_funds_runtime.gd. const HANDOFF_PATH := "res://artifacts/p05-02/restart-handoff.json" const HANDOFF_SCHEMA := "p05-02-restart-handoff-v1" const WRITER_SOURCE := "res://tests/economy/test_p05_funds_restart_1_write.gd" const WRITER_SAVE := "user://p05_restart_write/minute_%d" const READER_SAVE := "user://p05_restart_read/minute_%d" const READER_TAMPERED_SAVE := "user://p05_restart_read/changed" const SEED := 5025 # Mid-day 2, just before the day 2 00:00 close (which needs a hidden-fund top-up) # and just after it. const SAVE_MINUTES: Array[int] = [2400, 2850, 2880] const LAST_MINUTE := 4320 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" var _expect: Callable func _init(expect: Callable) -> void: _expect = expect func new_fixture() -> 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.call(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.call(bool(runtime.enable_production_operations().get(&"ok", false)), "fixture enables production Operations") for account: StringName in [FRONT_SALES, BACK_SALES, PAYER]: runtime.economy_service.register_account(account) runtime.economy_service.register_account(LOAN, true) _expect.call(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() ## Applies the day script's steps after `after_minute` through `through_minute` ## and calls `on_minute` after each step (the writer saves there). ## Day 1: borrow 1000, hidden sale 700 (tax 70, interest 10). Day 2: a cost leaves ## 5 front funds for the 10 interest, so hidden funds top up 5. Day 3: front sale ## 50 (tax 5, interest 10). Final: front 35, hidden 695. func play(fixture: Dictionary, after_minute: int, through_minute: int, on_minute: Callable) -> void: 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", 5915, &"OPERATING_EXPENSE", "d2.cost")], [2400, Callable()], [2850, 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]) if minute <= after_minute or minute > through_minute: continue _advance_to(fixture, minute) if (step[1] as Callable).is_valid(): (step[1] as Callable).call() if on_minute.is_valid(): on_minute.call(minute) func economy_bytes(runtime: Variant) -> PackedByteArray: return var_to_bytes(runtime.create_snapshot()[&"systems"][&"runtime"][&"economy"]) ## Operations history in the transport-safe form the Runtime's own restore ## crosslink compares (StringName ids become String values in saves). func days_value(runtime: Variant) -> Variant: return runtime.call("_operations_snapshot_comparison_value", runtime.operations_coordinator.get_day_summaries()) func days_equal(runtime: Variant, expected: Variant) -> bool: return runtime.call("_strict_variants_equal", expected, days_value(runtime)) func category_count(runtime: Variant, categories: Array) -> int: var count := 0 for entry: LedgerEntry in runtime.economy_service.get_entries(): if entry.category in categories: count += 1 return count static func sha256(bytes: PackedByteArray) -> String: var context := HashingContext.new() context.start(HashingContext.HASH_SHA256) if not bytes.is_empty(): context.update(bytes) return context.finish().hex_encode() # Every Operations boundary is on a 30-minute mark; each step ends on one. func _advance_to(fixture: Dictionary, minute: int) -> void: var clock: Variant = fixture[&"clock"] var runtime: Variant = fixture[&"runtime"] while int(clock.absolute_minute) < minute: var step := mini(30 - int(clock.absolute_minute) % 30, minute - int(clock.absolute_minute)) 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) # 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.call(false, "accelerated fixture cannot skip live elevator movement") return transport_snapshot[&"current_minute"] = minute if not runtime.elevators.restore_snapshot(transport_snapshot): _expect.call(false, "empty transport clock aligns through validated snapshot restore") func _sale(fixture: Dictionary, revenue_account: StringName, fund: StringName, amount: int, key: String) -> void: 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"), ] _expect.call(runtime.economy_service.post(entries, StringName("p05.sale." + key)).is_success(), "fixture sale " + key) func _move(fixture: Dictionary, from: StringName, to: StringName, amount: int, category: StringName, key: String) -> void: var runtime: Variant = fixture[&"runtime"] _expect.call(runtime.economy_service.transfer(from, to, amount, fixture[&"clock"].absolute_minute, category, &"p05.fixture", StringName("p05.move." + key)).is_success(), "fixture move " + key)