straywild/tests/bottle_message_state_validation.gd

187 lines
6 KiB
GDScript

extends SceneTree
const BottleMessageCatalogType = preload(
"res://messages/bottle_message_catalog.gd"
)
const BottleMessageDataType = preload(
"res://messages/bottle_message_data.gd"
)
const PlayerBottleMessageStateType = preload(
"res://messages/player_bottle_message_state.gd"
)
const CATALOG_PATH: String = (
"res://messages/catalog/bottle_message_catalog.tres"
)
const BASE_TIME: int = 1788282000
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_validate_shipped_catalog()
_validate_player_save_migration()
_validate_deterministic_unseen_selection()
_validate_cooldown_and_assignment()
_validate_save_round_trip_and_unknown_ids()
_validate_backward_clock_tolerance()
print("Bottle message state validation: PASS")
quit()
func _validate_shipped_catalog() -> void:
var catalog := load(CATALOG_PATH) as BottleMessageCatalogType
assert(catalog != null)
var message: BottleMessageDataType = catalog.get_message(
&"pushing_squares"
)
assert(message != null)
assert(message.active)
assert(message.body == "it's as simple as pushing squares")
var message_2: BottleMessageDataType = catalog.get_message(
&"message_2"
)
assert(message_2 != null)
assert(message_2.active)
assert(
message_2.body
== "01101001 00100000 01101100 01101111 01110110 01100101 00100000 01111001 01101111 01110101"
)
func _validate_player_save_migration() -> void:
var manager := PlayerSaveManager.new()
root.add_child(manager)
var migrated: Dictionary = manager.call(
"_migrate_save",
{"save_version": 11},
11,
)
assert(int(migrated.get("save_version", -1)) == 13)
assert(
migrated.get("message_bottles", {})
== PlayerBottleMessageStateType.default_save_data()
)
manager.queue_free()
func _validate_deterministic_unseen_selection() -> void:
var catalog := BottleMessageCatalogType.new()
for message_id: StringName in [&"third", &"first", &"second"]:
var message := BottleMessageDataType.new()
message.message_id = message_id
message.body = String(message_id)
catalog.entries.append(message)
var first_rng := RandomNumberGenerator.new()
first_rng.seed = 4412
var second_rng := RandomNumberGenerator.new()
second_rng.seed = 4412
var received: Array[StringName] = [&"second"]
assert(
catalog.choose_random_unseen_message(received, first_rng).message_id
== catalog.choose_random_unseen_message(received, second_rng).message_id
)
catalog.get_message(&"first").active = false
assert(
catalog.choose_random_unseen_message(received, first_rng).message_id
== &"third"
)
func _validate_cooldown_and_assignment() -> void:
var catalog := load(CATALOG_PATH) as BottleMessageCatalogType
var state := PlayerBottleMessageStateType.new()
root.add_child(state)
var rng := RandomNumberGenerator.new()
rng.seed = 915
assert(state.ensure_cooldown_scheduled(BASE_TIME, rng))
var due_at: int = state.get_next_bottle_at_unix()
assert(
due_at >= BASE_TIME + state.MINIMUM_COOLDOWN_SECONDS
and due_at <= BASE_TIME + state.MAXIMUM_COOLDOWN_SECONDS
)
assert(state.try_assign_due_message(catalog, rng, due_at - 1) == null)
var message: BottleMessageDataType = state.try_assign_due_message(
catalog, rng, due_at
)
assert(message != null)
assert(state.has_pending_message())
assert(not state.is_pending_bottle_picked_up())
assert(state.has_received_message(message.message_id))
assert(state.get_next_bottle_at_unix() == 0)
assert(not state.ensure_cooldown_scheduled(due_at, rng))
assert(state.try_assign_due_message(catalog, rng, due_at) == null)
assert(state.record_bottle_picked_up(due_at + 10, rng))
assert(state.is_pending_bottle_picked_up())
assert(
state.get_next_bottle_at_unix()
>= due_at + 10 + state.MINIMUM_COOLDOWN_SECONDS
)
assert(not state.record_bottle_picked_up(due_at + 20, rng))
assert(state.consume_pending_message() == message.message_id)
assert(not state.has_pending_message())
var second_due_at: int = state.get_next_bottle_at_unix()
var second_message: BottleMessageDataType = state.try_assign_due_message(
catalog,
rng,
second_due_at,
)
assert(second_message != null)
assert(second_message.message_id != message.message_id)
assert(state.record_bottle_picked_up(second_due_at + 10, rng))
assert(state.consume_pending_message() == second_message.message_id)
var exhausted_due_at: int = state.get_next_bottle_at_unix()
assert(
state.try_assign_due_message(
catalog,
rng,
exhausted_due_at,
) == null
)
state.queue_free()
func _validate_save_round_trip_and_unknown_ids() -> void:
var state := PlayerBottleMessageStateType.new()
root.add_child(state)
var data := {
"schema_version": state.SAVE_SCHEMA_VERSION,
"received_message_ids": ["retired_message"],
"pending_message_id": "pending_from_future_catalog",
"pending_bottle_picked_up": true,
"next_bottle_at_unix": BASE_TIME + 50,
"last_observed_unix": BASE_TIME,
}
assert(state.restore_from_save_data(data))
assert(state.has_received_message(&"retired_message"))
assert(state.has_received_message(&"pending_from_future_catalog"))
assert(state.get_pending_message_id() == &"pending_from_future_catalog")
assert(state.is_pending_bottle_picked_up())
var saved: Dictionary = state.to_save_data()
assert(
"retired_message" in (saved["received_message_ids"] as Array)
)
assert(
"pending_from_future_catalog"
in (saved["received_message_ids"] as Array)
)
state.queue_free()
func _validate_backward_clock_tolerance() -> void:
var state := PlayerBottleMessageStateType.new()
root.add_child(state)
var rng := RandomNumberGenerator.new()
rng.seed = 122
assert(state.ensure_cooldown_scheduled(BASE_TIME, rng))
var original_due_at: int = state.get_next_bottle_at_unix()
assert(not state.observe_wall_clock(BASE_TIME - 60))
assert(state.get_last_observed_unix() == BASE_TIME)
assert(state.get_next_bottle_at_unix() == original_due_at)
var rollback: int = state.BACKWARD_CLOCK_TOLERANCE_SECONDS + 1
assert(state.observe_wall_clock(BASE_TIME - rollback))
assert(state.get_last_observed_unix() == BASE_TIME - rollback)
assert(state.get_next_bottle_at_unix() == original_due_at - rollback)
state.queue_free()