Files
TheTree_mock/tests/helpers/p05_funds_restart_fixture.gd

162 lines
7.1 KiB
GDScript

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)