Add new tests and implement frame budget management

- Introduced `civilization_benchmark.gd` and `legacy_quantity_math.gd` for benchmarking and legacy quantity calculations.
- Created `test_dynamic_math.gd`, `test_frame_evolution.gd`, and `test_sparse_batches.gd` to validate mathematical operations and frame evolution logic.
- Implemented `frame_budget.gd` to manage cooperative work on the main thread, ensuring no partially calculated state is published.
- Added UID files for all new scripts to maintain unique identifiers.
This commit is contained in:
Kirill
2026-10-01 23:43:34 +03:00
parent f51eea6c0f
commit c4e7302567
53 changed files with 1313 additions and 397 deletions
+29 -6
View File
@@ -6,7 +6,7 @@ const E = preload("res://world/owniverse/math/exact.gd")
const SETTING_SECTION = "Settings"
const SAVE_PATH = "user://settings.cfg"
const FORMAT_VERSION : int = 4
const FORMAT_VERSION : int = 5
const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
const STATE_VERSION : String = SimulationTime.STATE_VERSION
@@ -35,7 +35,7 @@ func _snapshot(filename: String, password: String, data: Dictionary) -> Dictiona
var payload: Dictionary = E.encode(data)
payload.format_version = FORMAT_VERSION
payload.app_version = str(ProjectSettings.get_setting("application/config/version", ""))
return {"filename": filename, "path": save_directory.path_join(E.add(filename, ".adventure")), "password": password, "payload": payload, "plaintext": Global.save_plaintext_copy, "speed_limit": Global.SPEED_LIMIT, "stages": Global.CivilizationRules.keys()}
return {"filename": filename, "path": save_directory.path_join(E.add(filename, ".adventure")), "password": password, "payload": payload, "plaintext": Global.save_plaintext_copy, "speed_limit": Global.SPEED_LIMIT, "stages": Global.CivilizationRules.keys(), "entities": Global.OwniverseEntities.keys()}
## True means queued, not written. Completion is reported by save_completed.
@@ -105,7 +105,7 @@ func save_data(filename: String, password: String, data: Dictionary) -> bool:
static func _write_snapshot(job: Dictionary) -> String:
var payload: Dictionary = job.payload
var problem: String = _validate_payload(payload, job.speed_limit, job.stages)
var problem: String = _validate_payload(payload, job.speed_limit, job.stages, job.entities)
if E.ne(problem, ""):
return E.add("Invalid save: ", problem)
var path: String = job.path
@@ -207,17 +207,17 @@ static func _plain_tree(value: Variant) -> bool:
## Return a diagnostic path on failure; an empty string means the structure is valid.
func validate_data(data: Variant) -> String:
return _validate_payload(data, Global.SPEED_LIMIT, Global.CivilizationRules.keys())
return _validate_payload(data, Global.SPEED_LIMIT, Global.CivilizationRules.keys(), Global.OwniverseEntities.keys())
static func _validate_payload(data: Variant, speed_limit: int, stages: Array) -> String:
static func _validate_payload(data: Variant, speed_limit: int, stages: Array, entities: Array) -> String:
if not E.valid_encoded(data): return "invalid exact number encoding"
data = E.decode(data)
if ((not (data is Dictionary)) or (not _plain_tree(data))):
return "save must contain plain finite values"
if (E.ne(data.get("format_version"), FORMAT_VERSION) or (not (data.get("app_version") is String))):
return "unsupported save format"
if (((not (data.get("ui") is Dictionary)) or (not (data.ui.get("current_page") is int))) or E.lt(data.ui.current_page, 0)):
if (((not (data.get("ui") is Dictionary)) or (not E.numeric(data.ui.get("current_page")))) or E.lt(data.ui.current_page, 0)):
return "invalid ui.current_page"
if (not (data.get("state") is Dictionary)):
return "state must be a Dictionary"
@@ -258,6 +258,29 @@ static func _validate_payload(data: Variant, speed_limit: int, stages: Array) ->
return "invalid state.speed"
if ((not _number(saved.speed_in_year)) or E.ne(saved.speed_in_year, E.power(10, E.subtract(saved.speed, 1)))):
return "invalid state.speed_in_year"
if data.has("simulation"):
var checkpoint: Variant = data.simulation
if not checkpoint is Dictionary or not checkpoint.get("productions") is Array:
return "invalid simulation checkpoint"
if not _number(checkpoint.get("speed"), true) or E.lt(checkpoint.speed, 1) or E.gt(checkpoint.speed, speed_limit):
return "invalid simulation speed"
for name in ["requested_clock", "active_clock"]:
var clock: Variant = checkpoint.get(name)
if not clock is Dictionary: return "invalid simulation clock"
if clock.is_empty(): continue
if not _number(clock.get("datetime")) or not _number(clock.get("year")):
return "invalid simulation clock"
if E.lt(clock.datetime, saved.time.datetime) or E.ne(clock.year, SimulationTime.processed_year(clock.datetime)):
return "invalid simulation clock"
if not E.finite(clock.get("tick_remainder", 0)) or E.ge(E.absolute(clock.get("tick_remainder", 0)), 1):
return "invalid simulation tick"
if not checkpoint.active_clock.is_empty():
if checkpoint.requested_clock.is_empty() or E.gt(checkpoint.active_clock.datetime, checkpoint.requested_clock.datetime):
return "invalid simulation clock order"
for command in checkpoint.productions:
if not command is Dictionary or not entities.has(command.get("id")) or not command.get("resource") is bool or not _number(command.get("amount")):
return "invalid queued production"
if command.resource and command.id not in ["S", "H"]: return "invalid queued resource production"
if ((not _number(saved.energy, true)) or (not _number_map(saved.amount, true))):
return "invalid state resources"
if ((not (saved.stats is Dictionary)) or (not _number_map(saved.stats.get("total_amount"), true))):