extends RefCounted ## Weather state and scheduled transitions. The caller owns generation policy; ## this model invents neither probabilities nor rain/fog balance multipliers. const VERSION := 1 const KINDS := [&"clear", &"rain", &"storm", &"mana_fog"] const MAX_MINUTE := 2147483647 const MAX_PENDING := 4096 var _minute := -1 var _kind: StringName = &"" var _since := -1 var _pending: Array[Dictionary] = [] func initialize(kind: Variant, minute: Variant) -> bool: if _minute >= 0 or not _valid_kind(kind) or not _valid_minute(minute): return false _minute = minute _since = minute _kind = StringName(kind) return true func get_kind() -> StringName: return _kind func get_minute() -> int: return _minute func permits_new_arrivals() -> bool: return _minute >= 0 and _kind != &"storm" func schedule(kind: Variant, minute: Variant) -> Dictionary: if _minute < 0 or not _valid_kind(kind) or not _valid_minute(minute) or minute <= _minute: return {&"ok": false, &"code": &"INVALID_TRANSITION"} for row: Dictionary in _pending: if row[&"minute"] == minute: return {&"ok": row[&"kind"] == StringName(kind), &"replayed": row[&"kind"] == StringName(kind)} if _pending.size() >= MAX_PENDING: return {&"ok": false, &"code": &"SCHEDULE_FULL"} _pending.append({&"kind": StringName(kind), &"minute": minute}) _pending.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"minute"] < b[&"minute"]) return {&"ok": true, &"replayed": false} func advance_to(minute: Variant) -> Dictionary: if _minute < 0 or not _valid_minute(minute) or minute < _minute: return {&"ok": false, &"code": &"INVALID_TIME"} var changes: Array[Dictionary] = [] var replayed: bool = minute == _minute while not _pending.is_empty() and int(_pending[0][&"minute"]) <= minute: var transition: Dictionary = _pending.pop_front() if transition[&"kind"] == _kind: continue changes.append({&"from": _kind, &"to": transition[&"kind"], &"minute": transition[&"minute"]}) _kind = transition[&"kind"] _since = transition[&"minute"] _minute = minute return {&"ok": true, &"replayed": replayed, &"changes": changes} func create_snapshot() -> Dictionary: return {&"version": VERSION, &"minute": _minute, &"kind": _kind, &"since": _since, &"pending": _pending.duplicate(true)} func restore_snapshot(source: Dictionary) -> bool: if not _keys(source, ["version", "minute", "kind", "since", "pending"]) \ or typeof(source["version"]) != TYPE_INT or source["version"] != VERSION \ or not _valid_minute(source["minute"]) or not _valid_minute(source["since"]) \ or source["since"] > source["minute"] or not _valid_kind(source["kind"]) \ or not source["pending"] is Array or source["pending"].size() > MAX_PENDING: return false var previous: int = source["minute"] var pending: Array[Dictionary] = [] for value: Variant in source["pending"]: if not value is Dictionary or not _keys(value, ["kind", "minute"]) \ or not _valid_kind(value["kind"]) or not _valid_minute(value["minute"]) \ or value["minute"] <= previous: return false previous = value["minute"] pending.append({&"kind": StringName(value["kind"]), &"minute": value["minute"]}) _minute = source["minute"] _kind = StringName(source["kind"]) _since = source["since"] _pending = pending return true static func _valid_kind(value: Variant) -> bool: return typeof(value) in [TYPE_STRING, TYPE_STRING_NAME] and StringName(value) in KINDS static func _valid_minute(value: Variant) -> bool: return typeof(value) == TYPE_INT and value >= 0 and value <= MAX_MINUTE static func _keys(source: Dictionary, expected: Array) -> bool: if source.size() != expected.size(): return false for key: Variant in source: if typeof(key) not in [TYPE_STRING, TYPE_STRING_NAME] or String(key) not in expected: return false return true