Files
TheTree_mock/scripts/core/simulation_snapshot.gd

65 lines
2.5 KiB
GDScript

class_name SimulationSnapshot
extends RefCounted
const SCHEMA_VERSION := 1
static func capture(clock: SimulationKernel, state: Node, extra := {}) -> Dictionary:
var snapshot := {
&"schema_version": SCHEMA_VERSION,
&"clock": clock.clock_snapshot(),
&"game_state": state.create_snapshot(),
}
if extra is Dictionary and not extra.is_empty():
snapshot[&"systems"] = extra.duplicate(true)
return snapshot
static func validate(snapshot: Dictionary) -> Dictionary:
var errors: Array[String] = []
var schema_value: Variant = snapshot.get(&"schema_version", null)
if not _is_nonnegative_integer(schema_value) or int(schema_value) != SCHEMA_VERSION:
errors.append("unsupported_schema_version")
if not snapshot.get(&"clock", null) is Dictionary:
errors.append("missing_clock")
else:
var clock_data: Dictionary = snapshot[&"clock"]
if not _is_nonnegative_integer(clock_data.get(&"absolute_minute", null)):
errors.append("invalid_absolute_minute")
var speed_value: Variant = clock_data.get(&"speed", null)
if not _is_nonnegative_integer(speed_value) or int(speed_value) not in [1, 2, 3, 4]:
errors.append("invalid_speed")
# Legacy envelopes may omit these fields and retain restore_clock's
# paused=true/version=0 defaults. Present fields cannot be coerced.
if typeof(clock_data.get(&"paused", true)) != TYPE_BOOL:
errors.append("invalid_paused")
if not _is_nonnegative_integer(clock_data.get(&"snapshot_version", 0)):
errors.append("invalid_snapshot_version")
if not snapshot.get(&"game_state", null) is Dictionary:
errors.append("missing_game_state")
return {
&"ok": errors.is_empty(),
&"errors": errors,
}
static func _is_nonnegative_integer(value: Variant) -> bool:
if typeof(value) == TYPE_INT:
return value >= 0
# JSON numbers are floats, but exact integral values remain compatible.
# Avoid an overflowing float-to-int conversion at the signed int64 limit.
if typeof(value) != TYPE_FLOAT or not is_finite(value):
return false
return value >= 0 and value < 9223372036854775808.0 and value == floor(value)
static func restore(snapshot: Dictionary, clock: SimulationKernel, state: Node) -> bool:
var validation := validate(snapshot)
if not validation[&"ok"]:
return false
var previous_clock := clock.clock_snapshot()
var previous_state: Dictionary = state.create_snapshot()
if not clock.restore_clock(snapshot[&"clock"]):
return false
if not state.restore_snapshot(snapshot[&"game_state"]):
clock.restore_clock(previous_clock)
state.restore_snapshot(previous_state)
return false
return true