Files
TheTree_mock/tests/mvp/test_runtime_operations_acceptance.gd
T

495 lines
18 KiB
GDScript

extends SceneTree
const FOURTEEN_DAYS_MINUTES := 14 * SimulationKernel.MINUTES_PER_DAY
const RUNTIME_SNAPSHOT_VERSION := 9
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("MVP: production Operations acceptance")
_test_waiting_choice_and_command_exactly_once()
_test_twenty_four_hour_actual_tick_smoke()
_test_full_partial_payroll_bankruptcy_and_v9_tamper()
_test_fourteen_day_boundary_snapshot_determinism()
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 _new_fixture(seed: int, enable_operations := true) -> 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()
var initialized: Dictionary = runtime.initialize(clock, state, seed)
clock.set_paused(true)
_expect(bool(initialized.get(&"valid", false)), "fixture Runtime initializes")
var initial_snapshot: Dictionary = runtime.create_snapshot()
_expect(
int(initial_snapshot[&"systems"][&"runtime"].get(
&"runtime_schema_version", -1
)) == RUNTIME_SNAPSHOT_VERSION,
"fixture emits Runtime snapshot schema v9"
)
if enable_operations:
var enabled: Dictionary = runtime.enable_production_operations()
_expect(bool(enabled.get(&"ok", false)), "fixture enables production Operations")
return {&"clock": clock, &"state": state, &"runtime": runtime}
func _free_fixture(fixture: Dictionary) -> void:
var runtime: Variant = fixture.get(&"runtime")
var clock: Variant = fixture.get(&"clock")
var state: Variant = fixture.get(&"state")
if runtime != null:
runtime.free()
if clock != null:
clock.free()
if state != null:
state.free()
# Long acceptance slices deliberately exercise the production Operations
# adapter without claiming to execute unrelated actor AI on all 20,160 minutes.
# Aggregate clocks are aligned solely to preserve the Runtime transaction
# precondition; the separate 24-hour smoke above covers the actual Tick pipeline.
# One final rich snapshot (and the midpoint restore) is the Operations oracle.
func _advance_operations_boundary_slice(fixture: Dictionary, amount: int) -> int:
var clock: Variant = fixture[&"clock"]
var runtime: Variant = fixture[&"runtime"]
var advanced := 0
while advanced < amount:
if runtime.operations_coordinator != null \
and runtime.operations_coordinator.is_terminal():
break
# Operations has no recurring boundary finer than 30 minutes. The
# coordinator still performs its own unthinned internal minute traversal;
# unrelated Runtime AI is intentionally outside this accelerated oracle.
var step := mini(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_operations_time", target)
if bool(runtime.get_operations_report().get(&"runtime_failure_latched", false)):
break
if runtime.operations_coordinator.is_terminal():
# A terminal daily close commits TIME_BOUNDARIES and deliberately skips
# the Runtime suffix; its other aggregates therefore remain at X - 1.
_align_operations_fixture_clocks(runtime, target - 1)
advanced += step
break
# These acceptance fixtures contain no Facilities, logistics, raids, or
# Staff tasks. Their clocks are aligned for Operations cross-link checks;
# their skipped actor/Shift behavior is explicitly outside this fast oracle.
_align_operations_fixture_clocks(runtime, target)
advanced += step
return advanced
func _align_operations_fixture_clocks(runtime: Variant, minute: int) -> void:
runtime.inventory_service.current_minute = minute
runtime.facility_service.current_minute = minute
runtime.kitchen_production.current_minute = minute
runtime.guest_service.current_minute = minute
runtime.staff_service.current_minute = minute
runtime.authority_service.current_minute = minute
runtime.defense_service.current_minute = minute
runtime.construction_service.current_minute = minute
runtime.excavation_service.current_minute = minute
# This accelerated oracle owns no cars or rides; update its empty transport
# aggregate through the public snapshot validator instead of a private clock.
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 Operations oracle 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 _operations_slice_is_healthy(fixture: Dictionary) -> bool:
var runtime: Variant = fixture[&"runtime"]
var report: Dictionary = runtime.get_operations_report()
var foreground: EventInstance = runtime.event_service.get_foreground_instance()
return not bool(report.get(&"runtime_failure_latched", true)) \
and int(report.get(&"deferred_runtime_minute", -2)) == -1 \
and not runtime.operations_coordinator.pause_requested \
and not runtime.operations_coordinator.is_terminal() \
and not runtime.operations_coordinator.is_external_quarantined() \
and (foreground == null or foreground.state != EventInstance.State.WAITING_CHOICE)
func _test_waiting_choice_and_command_exactly_once() -> void:
var fixture := _new_fixture(73_001)
var runtime: Variant = fixture[&"runtime"]
var clock: Variant = fixture[&"clock"]
var state: Variant = fixture[&"state"]
var queued: CommandResult = runtime.event_service.enqueue_candidate(
&"relationship.threshold",
[],
clock.absolute_minute,
{&"deduplication_key": &"operations-acceptance-choice"}
)
var started: CommandResult = runtime.event_service.start_next_event(
clock.absolute_minute, {}
)
_expect(
queued.is_success() and started.is_success()
and bool(started.details.get(&"waiting_for_choice", false)),
"foreground Event enters WAITING_CHOICE"
)
clock.set_paused(false)
var before_choice_minute: int = clock.absolute_minute
clock.advance_minutes(5)
var foreground: EventInstance = runtime.event_service.get_foreground_instance()
_expect(
clock.paused and clock.absolute_minute == before_choice_minute + 1,
"WAITING_CHOICE pauses at the first committed minute and stops catch-up"
)
_expect(
foreground != null and foreground.state == EventInstance.State.WAITING_CHOICE,
"paused foreground ownership remains intact"
)
if foreground != null:
var choice_result: CommandResult = runtime.submit_command(
&"submit_event_choice",
{
&"event_instance_id": foreground.event_instance_id,
&"choice_instance_id": foreground.choice_instance_id,
&"choice_id": &"acknowledge",
},
&"player",
foreground.event_instance_id,
100,
-1,
&"operations.acceptance.choice"
)
_expect(
choice_result.is_success()
and runtime.event_service.get_foreground_instance() == null
and not clock.paused,
"submitting the stable Choice token resolves the Event and resumes time"
)
clock.set_paused(true)
var gold_before: int = state.get_resource(&"gold")
var first: CommandResult = runtime.submit_command(
&"change_resources",
{&"deltas": {&"gold": 17}},
&"player",
&"",
50,
-1,
&"operations.acceptance.exactly-once"
)
var gold_after_first: int = state.get_resource(&"gold")
var retry: CommandResult = runtime.submit_command(
&"change_resources",
{&"deltas": {&"gold": 17}},
&"player",
&"",
50,
-1,
&"operations.acceptance.exactly-once"
)
_expect(
first.is_success() and gold_after_first == gold_before + 17,
"first public command commits its resource delta once"
)
_expect(
not retry.is_success()
and retry.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY
and state.get_resource(&"gold") == gold_after_first,
"transport retry with the same idempotency key cannot apply twice"
)
var command_snapshot: Dictionary = runtime.create_snapshot()
_expect(
runtime.restore_snapshot(command_snapshot),
"command history survives an exact v9 snapshot round trip"
)
var restored_retry: CommandResult = runtime.submit_command(
&"change_resources",
{&"deltas": {&"gold": 17}},
&"player",
&"",
50,
-1,
&"operations.acceptance.exactly-once"
)
_expect(
not restored_retry.is_success()
and restored_retry.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY
and state.get_resource(&"gold") == gold_after_first,
"restored idempotency history still rejects the same command"
)
_free_fixture(fixture)
func _test_twenty_four_hour_actual_tick_smoke() -> void:
var fixture := _new_fixture(73_004)
var runtime: Variant = fixture[&"runtime"]
var clock: Variant = fixture[&"clock"]
var start_minute: int = clock.absolute_minute
clock.advance_minutes(SimulationKernel.MINUTES_PER_DAY)
_expect(
clock.absolute_minute - start_minute == SimulationKernel.MINUTES_PER_DAY,
"actual SimulationKernel ticks complete an uninterrupted 24-hour smoke slice"
)
_expect(
_operations_slice_is_healthy(fixture)
and runtime.operations_coordinator.current_minute == clock.absolute_minute
and runtime.operations_coordinator.get_day_summaries().size() == 1,
"24-hour actual-tick slice has no Choice, deferred minute, quarantine, or terminal stop"
)
var snapshot: Dictionary = runtime.create_snapshot()
_expect(
runtime.restore_snapshot(snapshot)
and runtime.latest_digest == SimulationDigest.compute(snapshot),
"24-hour actual-tick v9 snapshot round trip is exact"
)
_free_fixture(fixture)
func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
var fixture := _new_fixture(73_002, false)
var runtime: Variant = fixture[&"runtime"]
var clock: Variant = fixture[&"clock"]
var state: Variant = fixture[&"state"]
var candidate: Dictionary = runtime.get_mvp_staff_candidate_payload(&"staff_akane")
var daily_wage := int(candidate[&"options"][&"daily_wage"])
var hired: CommandResult = runtime.submit_command(
&"hire_staff_candidate",
{&"candidate_id": &"staff_akane", &"current_cell": Vector3i.ZERO},
&"player",
&"staff_akane",
80,
-1,
&"operations.acceptance.hire"
)
_expect(hired.is_success() and daily_wage > 50, "payroll fixture hires one wage-bearing Staff member")
var enabled: Dictionary = runtime.enable_production_operations()
_expect(bool(enabled.get(&"ok", false)), "payroll fixture enables Operations after the pre-opening hire")
var full_advanced := _advance_operations_boundary_slice(
fixture, SimulationKernel.MINUTES_PER_DAY
)
var full: Dictionary = runtime.staff_service.get_payroll_settlement(&"payroll-day-00000001")
_expect(
full_advanced == SimulationKernel.MINUTES_PER_DAY
and _operations_slice_is_healthy(fixture)
and clock.absolute_minute == SimulationKernel.MINUTES_PER_DAY + 6 * 60
and int(full.get(&"new_wages_total", -1)) == daily_wage
and int(full.get(&"paid_total", -1)) == daily_wage
and int(full.get(&"unpaid_after_total", -1)) == 0,
"Day 1 payroll is paid in full and closes at exactly 06:00"
)
var cash_now: int = runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT)
var drained: CommandResult = runtime.submit_command(
&"change_resources",
{&"deltas": {&"gold": -(cash_now - 50)}},
&"player",
&"",
90,
-1,
&"operations.acceptance.cash-drain"
)
_expect(
drained.is_success()
and runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT) == 50,
"fixture leaves exactly 50 Gold for a deterministic partial payroll"
)
var partial_advanced := _advance_operations_boundary_slice(
fixture, SimulationKernel.MINUTES_PER_DAY
)
var partial: Dictionary = runtime.staff_service.get_payroll_settlement(&"payroll-day-00000002")
_expect(
partial_advanced == SimulationKernel.MINUTES_PER_DAY
and _operations_slice_is_healthy(fixture)
and clock.absolute_minute == 2 * SimulationKernel.MINUTES_PER_DAY + 6 * 60
and int(partial.get(&"new_wages_total", -1)) == daily_wage
and int(partial.get(&"paid_total", -1)) == 50
and int(partial.get(&"unpaid_after_total", -1)) == daily_wage - 50,
"Day 2 payroll pays available Gold only and carries exact arrears"
)
var terminal_advanced := _advance_operations_boundary_slice(
fixture, 2 * SimulationKernel.MINUTES_PER_DAY
)
var coordinator: OperationsCoordinator = runtime.operations_coordinator
var last_three_negative := true
for day in [2, 3, 4]:
var summary: Dictionary = coordinator.get_day_summary(day)
last_three_negative = last_three_negative \
and int(summary.get(&"net_profit", 0)) < 0 \
and int((summary.get(&"settlement", {}) as Dictionary).get(
&"arrears_total", 0
)) > 0
_expect(
terminal_advanced == 2 * SimulationKernel.MINUTES_PER_DAY
and clock.absolute_minute == 4 * SimulationKernel.MINUTES_PER_DAY + 6 * 60
and last_three_negative
and coordinator.get_consecutive_insolvency_count() == 3
and coordinator.get_terminal_state() == &"BANKRUPT",
"three consecutive negative closes with arrears enter BANKRUPT at the third boundary"
)
var terminal_snapshot: Dictionary = runtime.create_snapshot()
var terminal_digest := SimulationDigest.compute(terminal_snapshot)
_expect(
runtime.restore_snapshot(terminal_snapshot),
"BANKRUPT terminal v9 snapshot restores without clearing terminal state"
)
clock.advance_minutes(180)
_expect(
clock.absolute_minute == 4 * SimulationKernel.MINUTES_PER_DAY + 6 * 60
and SimulationDigest.compute(runtime.create_snapshot()) == terminal_digest,
"BANKRUPT is terminal: later time requests are exact no-ops"
)
_expect(
not bool(runtime.acknowledge_operations_pause().get(&"ok", true)),
"BANKRUPT cannot be acknowledged away"
)
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"operations"][&"personal_action_points"] = 99,
"v9 rejects an out-of-range Operations action-point total"
)
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"next_command_id"] = int(
tampered[&"systems"][&"runtime"][&"next_command_id"]
) + 1,
"v9 rejects command high-water gaps"
)
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
var accepted: Array = tampered[&"systems"][&"runtime"][&"command_queue"][&"accepted"]
if not accepted.is_empty():
accepted[0][&"idempotency_key"] = &"tampered.accepted.key",
"v9 rejects accepted-command/idempotency cross-link tampering"
)
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
var summaries: Array = tampered[&"systems"][&"runtime"][&"operations"][&"coordinator"][&"day_summaries"]
summaries[0][&"category_totals"][&"wages"] = int(
summaries[0][&"category_totals"][&"wages"]
) + 1,
"v9 rejects DaySummary/payroll wage reconciliation tampering"
)
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"unexpected_v9_field"] = true,
"v9 rejects unknown Runtime fields"
)
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"operations"][&"coordinator"][&"unexpected_field"] = true,
"v9 rejects unknown Coordinator fields"
)
_free_fixture(fixture)
func _assert_v9_tamper_rejected(
runtime: Variant,
canonical: Dictionary,
mutator: Callable,
label: String
) -> void:
var before: Dictionary = runtime.create_snapshot()
var tampered: Dictionary = canonical.duplicate(true)
mutator.call(tampered)
var rejected: bool = not runtime.restore_snapshot(tampered)
_expect(rejected and runtime.create_snapshot() == before, label + " atomically")
func _test_fourteen_day_boundary_snapshot_determinism() -> void:
var seed := 73_003
var continuous := _new_fixture(seed)
var continuous_clock: Variant = continuous[&"clock"]
var continuous_runtime: Variant = continuous[&"runtime"]
var continuous_start: int = continuous_clock.absolute_minute
var first_advanced := _advance_operations_boundary_slice(
continuous, FOURTEEN_DAYS_MINUTES / 2
)
var midpoint: Dictionary = continuous_runtime.create_snapshot()
_expect(
first_advanced == FOURTEEN_DAYS_MINUTES / 2
and continuous_clock.absolute_minute == continuous_start + FOURTEEN_DAYS_MINUTES / 2
and _operations_slice_is_healthy(continuous),
"boundary-accelerated midpoint is captured after seven healthy days"
)
var second_advanced := _advance_operations_boundary_slice(
continuous, FOURTEEN_DAYS_MINUTES / 2
)
var continuous_snapshot: Dictionary = continuous_runtime.create_snapshot()
var continuous_digest := SimulationDigest.compute(continuous_snapshot)
_expect(
first_advanced + second_advanced == FOURTEEN_DAYS_MINUTES
and continuous_clock.absolute_minute - continuous_start == FOURTEEN_DAYS_MINUTES
and _operations_slice_is_healthy(continuous),
"continuous boundary-accelerated Operations advances exactly 14 days = 20,160 minutes"
)
_expect(
continuous_runtime.operations_coordinator.get_day_summaries().size() == 14,
"continuous boundary run commits all fourteen daily closes"
)
var resumed := _new_fixture(seed, false)
var resumed_clock: Variant = resumed[&"clock"]
var resumed_runtime: Variant = resumed[&"runtime"]
var restored: bool = resumed_runtime.restore_snapshot(midpoint)
_expect(restored, "fresh Runtime restores the enabled v9 midpoint snapshot")
var resumed_advanced := 0
if restored:
resumed_advanced = _advance_operations_boundary_slice(
resumed, FOURTEEN_DAYS_MINUTES / 2
)
var resumed_snapshot: Dictionary = resumed_runtime.create_snapshot()
_expect(
restored and resumed_advanced == FOURTEEN_DAYS_MINUTES / 2
and resumed_clock.absolute_minute == 6 * 60 + FOURTEEN_DAYS_MINUTES
and _operations_slice_is_healthy(resumed),
"restored boundary-accelerated Operations completes seven healthy days"
)
_expect(
restored and SimulationDigest.compute(resumed_snapshot) == continuous_digest
and resumed_snapshot == continuous_snapshot,
"continuous and midpoint-restored 14-day boundary runs have identical snapshot and Digest"
)
_free_fixture(continuous)
_free_fixture(resumed)
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)