Add exact arithmetic implementation and tests
- Introduced exact arithmetic with support for large integers and fractions in `exact.gd`, `big_integer.gd`, and `exact_value.gd`. - Implemented a comprehensive test suite in `test_exact_math.gd` to validate arithmetic operations against expected results. - Added a README in `math` directory to document the exact arithmetic system and its usage. - Ensured that all arithmetic operations maintain precision without floating-point errors, using a custom representation for large numbers. - Included methods for encoding and decoding exact values for serialization.
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
# Проверки поэтапного переноса из Owniverse.U
|
||||
|
||||
## Точная арифметика
|
||||
|
||||
`godot --headless --path Owniverse.GI --script res://tests/test_exact_math.gd`
|
||||
|
||||
Независимый эталон Python Fraction, границы int64 и 10^27, точная сериализация,
|
||||
рецепты с дробными стоимостями, сохранение сумм, время и 100 циклов с меняющимися
|
||||
долями. Все остальные тесты используют числовое сравнение и сериализацию через
|
||||
`E`, а не сравнение ссылок на большие числа. Границы точности и новый формат
|
||||
сохранения описаны в [документации арифметики](../world/owniverse/math/README.md).
|
||||
|
||||
## Модули симуляции
|
||||
|
||||
`godot --headless --path Owniverse.GI --script res://tests/test_module_lifecycle.gd`
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+22
-21
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
var failures: int = 0
|
||||
var checks: int = 0
|
||||
@@ -7,41 +8,41 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not value):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
var actions = load("res://ui/actions/actions.tscn").instantiate()
|
||||
root.add_child(actions)
|
||||
for maximum in [0.0, 0.1, 0.99, 1.0, 1.9, 10.0, 100.0, 123.0, 1000.0, 10000.0, 1e9, 1.1e9, 1e150]:
|
||||
for maximum in [0, E.of("0.1"), E.of("0.99"), 1, E.of("1.9"), 10, 100, 123, 1000, 10000, E.of("1e9"), E.of("1.1e9"), E.of("1e150")]:
|
||||
actions.set_actions("test", maximum)
|
||||
var labels: Array = []
|
||||
var values: Array = []
|
||||
for i in 5:
|
||||
var value: float = actions.action_values[i]
|
||||
var value: Variant = actions.action_values[i]
|
||||
var text: String = actions.action_labels[i].text
|
||||
check(value == floorf(value) and value <= floorf(maximum), "Whole affordable request")
|
||||
if value > 0.0:
|
||||
check(not values.has(value) and not labels.has(text), "Unique requests and displayed labels")
|
||||
check((E.eq(value, E.floor_value(value)) and E.le(value, E.floor_value(maximum))), "Whole affordable request")
|
||||
if E.gt(value, 0):
|
||||
check(((not values.has(value)) and (not labels.has(text))), "Unique requests and displayed labels")
|
||||
values.append(value)
|
||||
labels.append(text)
|
||||
else:
|
||||
check(text == "" and actions.action_labels[i].get_parent().get_node("TextureButton").disabled, "Unused button blank and disabled")
|
||||
if maximum >= 1.0: check(values.back() == floorf(maximum), "Maximum retained")
|
||||
actions.set_actions("test", 100.0)
|
||||
check(actions.action_values.values() == [1.0, 10.0, 100.0, 0.0, 0.0], "Exact power of ten not duplicated")
|
||||
actions.set_actions("SMBH", 25.0, func(_request): return 25.0)
|
||||
check(actions.action_values.values() == [25.0, 0.0, 0.0, 0.0, 0.0], "Identical SMBH previews collapse to one button")
|
||||
actions.set_actions("step", 123.0, func(request): return floorf(request / 5.0) * 5.0)
|
||||
check(actions.action_values.values() == [10.0, 100.0, 123.0, 0.0, 0.0], "Unproducible small requests omitted")
|
||||
check(actions.action_labels[2].text == "120", "Recipe preview displayed while original request retained")
|
||||
check((E.eq(text, "") and actions.action_labels[i].get_parent().get_node("TextureButton").disabled), "Unused button blank and disabled")
|
||||
if E.ge(maximum, 1): check(E.eq(values.back(), E.floor_value(maximum)), "Maximum retained")
|
||||
actions.set_actions("test", 100)
|
||||
check(E.eq(actions.action_values.values(), [1, 10, 100, 0, 0]), "Exact power of ten not duplicated")
|
||||
actions.set_actions("SMBH", 25, func ( _request ) : return 25)
|
||||
check(E.eq(actions.action_values.values(), [25, 0, 0, 0, 0]), "Identical SMBH previews collapse to one button")
|
||||
actions.set_actions("step", 123, func ( request ) : return E.multiply(E.floor_value(E.divide(request, 5)), 5))
|
||||
check(E.eq(actions.action_values.values(), [10, 100, 123, 0, 0]), "Unproducible small requests omitted")
|
||||
check(E.eq(actions.action_labels[2].text, "120"), "Recipe preview displayed while original request retained")
|
||||
var emitted: Array = []
|
||||
actions.action_pressed.connect(func(_page, amount): emitted.append(amount))
|
||||
actions.action_pressed.connect(func ( _page , amount ) : emitted.append(amount))
|
||||
actions._on_action_button_pressed(2)
|
||||
actions._on_action_button_pressed(4)
|
||||
check(emitted == [123.0], "Only active button emits its matching request")
|
||||
check(E.eq(emitted, [123]), "Only active button emits its matching request")
|
||||
actions.queue_free()
|
||||
print("Action values: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Action values: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+44
-39
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
var failures: int = 0
|
||||
var checks: int = 0
|
||||
@@ -8,89 +9,93 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not value):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
var manager = load("res://autoload/save_manager.gd").new()
|
||||
var directory: String = "/tmp/owniverse-async-%s-%s" % [OS.get_process_id(), Time.get_ticks_usec()]
|
||||
var directory: String = ("/tmp/owniverse-async-%s-%s" % [OS.get_process_id(), Time.get_ticks_usec()])
|
||||
DirAccess.make_dir_recursive_absolute(directory)
|
||||
manager.save_directory = directory
|
||||
root.add_child(manager)
|
||||
manager.save_completed.connect(func(filename, success, error): completions.append([filename, success, error]))
|
||||
manager.save_completed.connect(func ( filename , success , error ) : completions.append([filename, success, error]))
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.state.energy = E.subtract(world.state.energy, 1)
|
||||
world.state.time = preload("res://world/owniverse/simulation_time.gd").advance({"datetime": E.of("1e27")}, 0.000000001, 1)
|
||||
var data: Dictionary = {"state": world.state, "ui": {"current_page": 1}}
|
||||
var initial_energy: float = world.state.energy
|
||||
var initial_energy: Variant = world.state.energy
|
||||
var global: Node = root.get_node("Global")
|
||||
var old_plaintext: bool = global.save_plaintext_copy
|
||||
global.save_plaintext_copy = true
|
||||
check(manager.request_save("snapshot", "test-password", data), "Snapshot queued")
|
||||
data.state.energy = 99.0
|
||||
data.state.energy = 99
|
||||
data.ui.current_page = 2
|
||||
var frames: int = 0
|
||||
var deadline: int = Time.get_ticks_msec() + 10000
|
||||
while manager.is_saving() and Time.get_ticks_msec() < deadline:
|
||||
frames += 1
|
||||
var deadline: int = E.add(Time.get_ticks_msec(), 10000)
|
||||
while (manager.is_saving() and E.lt(Time.get_ticks_msec(), deadline)):
|
||||
frames = E.add(frames, 1)
|
||||
await process_frame
|
||||
check(not manager.is_saving() and frames > 0, "Frames continue while save is pending")
|
||||
check(((not manager.is_saving()) and E.gt(frames, 0)), "Frames continue while save is pending")
|
||||
var saved: Dictionary = manager.load_data("snapshot", "test-password")
|
||||
check(saved.state.energy == initial_energy and saved.ui.current_page == 1, "Worker owns a deep snapshot")
|
||||
check((E.eq(saved.state.energy, initial_energy) and E.eq(saved.ui.current_page, 1)), "Worker owns a deep snapshot")
|
||||
check(E.eq(saved.state.time, world.state.time), "Encrypted save preserves exact ticks at huge age")
|
||||
var plain: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("snapshot.adventure.json")))
|
||||
check(plain.state.energy == initial_energy, "Diagnostic JSON uses same snapshot")
|
||||
check(completions.size() == 1 and completions[0][1], "Completion reports successful write")
|
||||
check(E.eq(E.decode(plain.state.energy), initial_energy), "Diagnostic JSON uses same snapshot")
|
||||
check(E.eq(E.decode(plain.state.time), saved.state.time), "Diagnostic JSON preserves fractional time")
|
||||
check((E.eq(completions.size(), 1) and completions[0][1]), "Completion reports successful write")
|
||||
|
||||
completions.clear()
|
||||
data.state.energy = 1.0
|
||||
data.state.energy = 1
|
||||
manager.request_save("latest", "test-password", data)
|
||||
data.state.energy = 2.0
|
||||
data.state.energy = 2
|
||||
manager.request_save("latest", "test-password", data)
|
||||
data.state.energy = 3.0
|
||||
data.state.energy = 3
|
||||
manager.request_save("latest", "test-password", data)
|
||||
data.state.energy = 4.0
|
||||
data.state.energy = 4
|
||||
manager.flush_saves()
|
||||
check(completions.size() == 2, "Repeated requests coalesce behind active write")
|
||||
check(manager.load_data("latest", "test-password").state.energy == 3.0, "Last queued snapshot wins")
|
||||
check(E.eq(completions.size(), 2), "Repeated requests coalesce behind active write")
|
||||
check(E.eq(manager.load_data("latest", "test-password").state.energy, 3), "Last queued snapshot wins")
|
||||
|
||||
var incompatible_path: String = directory.path_join("incompatible.adventure")
|
||||
var incompatible = FileAccess.open_encrypted_with_pass(incompatible_path, FileAccess.WRITE, "test-password")
|
||||
incompatible.store_var({"format_version": -1, "state": {"unknown": true}}, false)
|
||||
incompatible.store_var({"format_version": E.neg(1), "state": {"unknown": true}}, false)
|
||||
incompatible.close()
|
||||
var original: PackedByteArray = FileAccess.get_file_as_bytes(incompatible_path)
|
||||
var previous_archive = FileAccess.open(incompatible_path + ".incompatible.bak", FileAccess.WRITE)
|
||||
var previous_archive = FileAccess.open(E.add(incompatible_path, ".incompatible.bak"), FileAccess.WRITE)
|
||||
previous_archive.store_string("keep previous backup")
|
||||
previous_archive.close()
|
||||
manager.blocked_saves.incompatible = true
|
||||
check(manager.load_data("incompatible", "test-password").is_empty() and manager.last_error == "", "Unsupported format permits a fresh session")
|
||||
check(not manager.blocked_saves.has("incompatible") and not FileAccess.file_exists(incompatible_path), "Incompatible file no longer blocks saving")
|
||||
check(FileAccess.get_file_as_bytes(incompatible_path + ".incompatible.bak.1") == original, "Unsupported save archived without losing bytes")
|
||||
check(FileAccess.get_file_as_string(incompatible_path + ".incompatible.bak") == "keep previous backup", "Earlier archive preserved")
|
||||
check((manager.load_data("incompatible", "test-password").is_empty() and E.eq(manager.last_error, "")), "Unsupported format permits a fresh session")
|
||||
check(((not manager.blocked_saves.has("incompatible")) and (not FileAccess.file_exists(incompatible_path))), "Incompatible file no longer blocks saving")
|
||||
check(E.eq(FileAccess.get_file_as_bytes(E.add(incompatible_path, ".incompatible.bak.1")), original), "Unsupported save archived without losing bytes")
|
||||
check(E.eq(FileAccess.get_file_as_string(E.add(incompatible_path, ".incompatible.bak")), "keep previous backup"), "Earlier archive preserved")
|
||||
check(manager.request_save("incompatible", "test-password", data), "New session can queue a save")
|
||||
manager.flush_saves()
|
||||
check(manager.load_data("incompatible", "test-password").state == data.state, "New format saves and reloads after incompatible file")
|
||||
check(E.eq(manager.load_data("incompatible", "test-password").state, data.state), "New format saves and reloads after incompatible file")
|
||||
|
||||
# Expected failures below must leave the last good encrypted file intact.
|
||||
data.state.energy = -1.0
|
||||
data.state.energy = E.neg(1)
|
||||
manager.request_save("latest", "test-password", data)
|
||||
manager.flush_saves()
|
||||
check(not completions.back()[1] and manager.last_error.contains("Invalid save"), "Worker validation error reaches main thread")
|
||||
check(manager.load_data("latest", "test-password").state.energy == 3.0, "Invalid snapshot preserves previous save")
|
||||
check(((not completions.back()[1]) and manager.last_error.contains("Invalid save")), "Worker validation error reaches main thread")
|
||||
check(E.eq(manager.load_data("latest", "test-password").state.energy, 3), "Invalid snapshot preserves previous save")
|
||||
manager.blocked_saves.latest = true
|
||||
check(not manager.request_save("latest", "test-password", data), "Unreadable save protection also applies to queue")
|
||||
check((not manager.request_save("latest", "test-password", data)), "Unreadable save protection also applies to queue")
|
||||
# A valid snapshot exercises an actual I/O failure, not validation.
|
||||
data.state.energy = 5.0
|
||||
data.state.energy = 5
|
||||
manager.request_save("missing/failure", "test-password", data)
|
||||
manager.flush_saves()
|
||||
check(manager.last_error.contains("Cannot write"), "I/O error reported without hanging queue")
|
||||
|
||||
manager.request_save("shutdown", "test-password", data)
|
||||
data.state.energy = 6.0
|
||||
data.state.energy = 6
|
||||
manager.request_save("shutdown", "test-password", data)
|
||||
manager.free()
|
||||
var reader = load("res://autoload/save_manager.gd").new()
|
||||
reader.save_directory = directory
|
||||
check(reader.load_data("shutdown", "test-password").state.energy == 6.0, "Teardown drains final pending snapshot")
|
||||
check(E.eq(reader.load_data("shutdown", "test-password").state.energy, 6), "Teardown drains final pending snapshot")
|
||||
reader.free()
|
||||
world.free()
|
||||
# Exercise the stage entry point too: manager tests alone do not catch a
|
||||
@@ -100,12 +105,12 @@ func run() -> void:
|
||||
shared_manager.save_directory = directory
|
||||
var stage = load("res://stage/stage.gd").new()
|
||||
stage.pending_page = 2
|
||||
stage.owniverse.state.energy = 42.0
|
||||
stage.owniverse.state.energy = 42
|
||||
stage.save_data()
|
||||
check(shared_manager.is_saving(), "Stage queues a background save instead of waiting for disk")
|
||||
stage.owniverse.state.energy = 43.0
|
||||
stage.owniverse.state.energy = 43
|
||||
shared_manager.flush_saves()
|
||||
check(shared_manager.load_data("owniverse", "qwerty").state.energy == 42.0, "Stage save writes an independent snapshot")
|
||||
check(E.eq(shared_manager.load_data("owniverse", "qwerty").state.energy, 42), "Stage save writes an independent snapshot")
|
||||
stage.owniverse.free()
|
||||
stage.free()
|
||||
shared_manager.save_directory = original_directory
|
||||
@@ -113,5 +118,5 @@ func run() -> void:
|
||||
for filename in DirAccess.get_files_at(directory):
|
||||
DirAccess.remove_absolute(directory.path_join(filename))
|
||||
DirAccess.remove_absolute(directory)
|
||||
print("Async save: %d checks, %d failures (validation/block/I/O errors above are intentional)" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Async save: %d checks, %d failures (validation/block/I/O errors above are intentional)" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -37,21 +38,21 @@ func dispose(world) -> void:
|
||||
|
||||
func run() -> void:
|
||||
var world = fixture()
|
||||
world.state.batch_origins.LGRB = 10357034.80000000074505806
|
||||
var boundary: float = 41215289.79999999701976776
|
||||
for interval in [[boundary - 2.5, boundary + 2.25], [boundary, boundary + 2.0]]:
|
||||
world.state.batch_origins.LGRB = E.of("10357034.80000000074505806")
|
||||
var boundary: Variant = E.of("41215289.79999999701976776")
|
||||
for interval in [[E.subtract(boundary, E.of("2.5")), E.add(boundary, E.of("2.25"))], [boundary, E.add(boundary, 2)]]:
|
||||
world.state.batches.LGRB = []
|
||||
world.batches.append_batch(world.state, "LGRB", 100.0, interval[0], interval[1])
|
||||
var total: float = 0.0
|
||||
var last: float = -INF
|
||||
for batch: Dictionary in world.state.batches.LGRB:
|
||||
total += batch.amount
|
||||
check(batch.created_from > last, "Batch windows advance monotonically")
|
||||
check(batch.created_to > batch.created_from, "Positive window duration")
|
||||
check(batch.amount >= 0.0, "No negative batch amounts")
|
||||
world.batches.append_batch(world.state, "LGRB", 100, interval[0], interval[1])
|
||||
var total: Variant = 0
|
||||
var last: Variant = E.neg(INF)
|
||||
for batch : Dictionary in world.state.batches.LGRB:
|
||||
total = E.add(total, batch.amount)
|
||||
check(E.gt(batch.created_from, last), "Batch windows advance monotonically")
|
||||
check(E.gt(batch.created_to, batch.created_from), "Positive window duration")
|
||||
check(E.ge(batch.amount, 0), "No negative batch amounts")
|
||||
last = batch.created_from
|
||||
check(total == 100.0, "Boundary correction preserves all objects")
|
||||
check(world.state.batches.LGRB.size() <= 7, "Finite number of windows")
|
||||
check(E.eq(total, 100), "Boundary correction preserves all objects")
|
||||
check(E.le(world.state.batches.LGRB.size(), 7), "Finite number of windows")
|
||||
dispose(world)
|
||||
print("Batch boundaries: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Batch boundaries: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -35,130 +36,130 @@ func dispose(world) -> void:
|
||||
entity.free()
|
||||
world.free()
|
||||
|
||||
func seed_population(world, id: String, count: float, time: float = 1.0) -> void:
|
||||
world.state.amount.N = 0.0
|
||||
func seed_population(world, id: String, count: Variant, time: Variant = 1.0) -> void:
|
||||
world.state.amount.N = 0
|
||||
world.state.amount[id] = count
|
||||
world.state.batches[id] = [{"amount": count, "start_year": time, "end_year": time}]
|
||||
world.evolution.initialize_life_batches()
|
||||
|
||||
func sum_ethics(world, id: String) -> float:
|
||||
var result: float = 0.0
|
||||
for n in world.state.ethics.get(id, {}).values(): result += n
|
||||
func sum_ethics(world, id: String) -> Variant:
|
||||
var result: Variant = 0
|
||||
for n in world.state.ethics.get(id, {}).values(): result = E.add(result, n)
|
||||
return result
|
||||
|
||||
func invariants(world) -> void:
|
||||
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
for id in world.state.ethics:
|
||||
check(sum_ethics(world, id) == physical.get(id, 0.0), "Ethics equal physical population for " + id)
|
||||
check(E.eq(sum_ethics(world, id), physical.get(id, 0)), E.add("Ethics equal physical population for ", id))
|
||||
for n in world.state.ethics[id].values():
|
||||
if n < 0.0 or n != floorf(n): check(false, "Invalid ethic amount")
|
||||
check(root.get_node("SaveManager").validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Civilization state validates")
|
||||
if (E.lt(n, 0) or E.ne(n, E.floor_value(n))): check(false, "Invalid ethic amount")
|
||||
check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Civilization state validates")
|
||||
|
||||
func run() -> void:
|
||||
var global: Node = root.get_node("Global")
|
||||
var baseline: Dictionary = global.CivilizationRules.duplicate(true)
|
||||
var world = fixture()
|
||||
seed_population(world, "C0.2", 60000.0)
|
||||
world.evolution.evolve_batches(world.state, 1.0)
|
||||
check(world.state.amount["C0.3"] > 0.0, "C0.2 actually progresses")
|
||||
check(world.state.amount["C0.1"] > 0.0, "C0.2 actually degrades")
|
||||
check(world.state.civilization_stats.get("wars", 0.0) == 0.0, "Table disables early wars")
|
||||
check(world.state.amount["C0.2"] + world.state.amount["C0.3"] + world.state.amount["C0.1"] == 60000.0, "Ordinary evolution conserves population")
|
||||
check(world.state.stats.total_amount["C0.2"] == 0.0, "Survivors do not count as new civilizations")
|
||||
check(world.state.civilization_stats.worship > 0.0, "Worship derived from surviving ethics")
|
||||
seed_population(world, "C0.2", 60000)
|
||||
world.evolution.evolve_batches(world.state, 1)
|
||||
check(E.gt(world.state.amount["C0.3"], 0), "C0.2 actually progresses")
|
||||
check(E.gt(world.state.amount["C0.1"], 0), "C0.2 actually degrades")
|
||||
check(E.eq(world.state.civilization_stats.get("wars", 0), 0), "Table disables early wars")
|
||||
check(E.eq(E.add(E.add(world.state.amount["C0.2"], world.state.amount["C0.3"]), world.state.amount["C0.1"]), 60000), "Ordinary evolution conserves population")
|
||||
check(E.eq(world.state.stats.total_amount["C0.2"], 0), "Survivors do not count as new civilizations")
|
||||
check(E.gt(world.state.civilization_stats.worship, 0), "Worship derived from surviving ethics")
|
||||
invariants(world)
|
||||
var restored = fixture()
|
||||
restored.state = bytes_to_var(var_to_bytes(world.state))
|
||||
world.evolution.evolve_batches(world.state, 2100.0)
|
||||
restored.evolution.evolve_batches(restored.state, 2100.0)
|
||||
check(world.state == restored.state, "Save resumes deterministic ethical evolution")
|
||||
restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
world.evolution.evolve_batches(world.state, 2100)
|
||||
restored.evolution.evolve_batches(restored.state, 2100)
|
||||
check(E.eq(world.state, restored.state), "Save resumes deterministic ethical evolution")
|
||||
invariants(world)
|
||||
dispose(restored)
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1e8
|
||||
seed_population(world, "C2.0", 60000.0)
|
||||
world.state.amount["C1.9"] = 10000.0
|
||||
world.state.amount.RD = 100000000
|
||||
seed_population(world, "C2.0", 60000)
|
||||
world.state.amount["C1.9"] = 10000
|
||||
world.evolution.initialize_life_batches()
|
||||
world.evolution.evolve_batches(world.state, 1.0)
|
||||
check(world.state.civilization_stats.wars > 0.0, "Wars execute when enabled")
|
||||
check(world.state.civilization_stats.collateral > 0.0, "Wars affect previous-stage populations")
|
||||
check(world.state.amount["C2.1"] > 0.0, "Winners and peaceful progress reach next stage")
|
||||
world.evolution.evolve_batches(world.state, 1)
|
||||
check(E.gt(world.state.civilization_stats.wars, 0), "Wars execute when enabled")
|
||||
check(E.gt(world.state.civilization_stats.collateral, 0), "Wars affect previous-stage populations")
|
||||
check(E.gt(world.state.amount["C2.1"], 0), "Winners and peaceful progress reach next stage")
|
||||
invariants(world)
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1.0
|
||||
world.state.binds.RD = {"HP": 5.0, "GG": 5.0, "AB": 5.0, "RP": 5.0}
|
||||
seed_population(world, "C1.6", 60000.0)
|
||||
for row: Dictionary in global.CivilizationRules["C1.6"].ethics.values(): row.degradation = "0"
|
||||
world.evolution.evolve_batches(world.state, 1.0)
|
||||
world.state.amount.RD = 1
|
||||
world.state.binds.RD = {"HP": 5, "GG": 5, "AB": 5, "RP": 5}
|
||||
seed_population(world, "C1.6", 60000)
|
||||
for row : Dictionary in global.CivilizationRules["C1.6"].ethics.values(): row.degradation = "0"
|
||||
world.evolution.evolve_batches(world.state, 1)
|
||||
var after: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
check(world.state.civilization_stats.colonies == 20.0, "Colonization limited to real targets")
|
||||
check(after.HP + after.GG + after.AB + after.RP == 0.0, "Colonized objects consumed once")
|
||||
check(world.state.binds.RD.get("C1.4", 0.0) == 20.0, "Colonies retain target host and use two-stages-earlier level")
|
||||
var totals: float = 0.0
|
||||
check(E.eq(world.state.civilization_stats.colonies, 20), "Colonization limited to real targets")
|
||||
check(E.eq(E.add(E.add(E.add(after.HP, after.GG), after.AB), after.RP), 0), "Colonized objects consumed once")
|
||||
check(E.eq(world.state.binds.RD.get("C1.4", 0), 20), "Colonies retain target host and use two-stages-earlier level")
|
||||
var totals: Variant = 0
|
||||
for id in after:
|
||||
if world.catalog.entities[id].group_as == "Planet": totals += after[id]
|
||||
check(totals == 60020.0, "Colonization replaces planets, does not create extra worlds")
|
||||
if E.eq(world.catalog.entities[id].group_as, "Planet"): totals = E.add(totals, after[id])
|
||||
check(E.eq(totals, 60020), "Colonization replaces planets, does not create extra worlds")
|
||||
invariants(world)
|
||||
dispose(world)
|
||||
|
||||
global.CivilizationRules = baseline.duplicate(true)
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1e25
|
||||
world.state.amount.BG = 1e25
|
||||
world.state.amount.TWD = 1e25
|
||||
world.state.amount.SNS = 1e25
|
||||
world.state.amount.MGT = 1e25
|
||||
world.state.amount.BH25 = 1e25
|
||||
world.state.amount.SMBH = 1e26
|
||||
world.state.amount.RD = E.of("1e25")
|
||||
world.state.amount.BG = E.of("1e25")
|
||||
world.state.amount.TWD = E.of("1e25")
|
||||
world.state.amount.SNS = E.of("1e25")
|
||||
world.state.amount.MGT = E.of("1e25")
|
||||
world.state.amount.BH25 = E.of("1e25")
|
||||
world.state.amount.SMBH = E.of("1e26")
|
||||
for id in world.catalog.entities:
|
||||
if world.catalog.entities[id].claim != "": world.state.amount[id] = 1e12
|
||||
if E.ne(world.catalog.entities[id].claim, ""): world.state.amount[id] = E.of("1e12")
|
||||
# Accelerated deterministic fixture traverses every enabled stage, including Ct.
|
||||
for id in global.CivilizationRules:
|
||||
if id == "Ct": continue
|
||||
if E.eq(id, "Ct"): continue
|
||||
global.CivilizationRules[id].fights = false
|
||||
global.CivilizationRules[id].colonizes = false
|
||||
global.CivilizationRules[id].diplomacy = false
|
||||
for row: Dictionary in global.CivilizationRules[id].ethics.values():
|
||||
for row : Dictionary in global.CivilizationRules[id].ethics.values():
|
||||
row.progress = "1"
|
||||
row.degradation = "0"
|
||||
world.catalog.entities[id].lifespan = 1.0
|
||||
world.catalog.entities[id].lifespan_delta = 1.0
|
||||
global.CivilizationRules[id].span = 1.0
|
||||
seed_population(world, "C0.2", 6.0)
|
||||
world.evolution.evolve_batches(world.state, 1000.0)
|
||||
check(world.state.amount.Ct == 6.0, "Complete C0.2 -> C4.4 -> Ct chain runs")
|
||||
check(not world.state.batches.has("Ct"), "Transcendence is terminal")
|
||||
world.catalog.entities[id].lifespan = 1
|
||||
world.catalog.entities[id].lifespan_delta = 1
|
||||
global.CivilizationRules[id].span = 1
|
||||
seed_population(world, "C0.2", 6)
|
||||
world.evolution.evolve_batches(world.state, 1000)
|
||||
check(E.eq(world.state.amount.Ct, 6), "Complete C0.2 -> C4.4 -> Ct chain runs")
|
||||
check((not world.state.batches.has("Ct")), "Transcendence is terminal")
|
||||
invariants(world)
|
||||
dispose(world)
|
||||
global.CivilizationRules = baseline
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 10000.0
|
||||
world.state.amount.SCS = 1.0
|
||||
world.state.amount["C2.4"] = 1.0
|
||||
world.state.amount.RD = 10000
|
||||
world.state.amount.SCS = 1
|
||||
world.state.amount["C2.4"] = 1
|
||||
world.evolution.initialize_life_batches()
|
||||
var span: float = world.state.batches["C2.4"][0].start_year
|
||||
check(is_equal_approx(span, 20000.0 / 3.6), "Structure bonus shortens newly registered lifespan")
|
||||
var span: Variant = world.state.batches["C2.4"][0].start_year
|
||||
check(E.near(span, E.divide(20000, E.of("3.6"))), "Structure bonus shortens newly registered lifespan")
|
||||
dispose(world)
|
||||
var evolution = load("res://world/owniverse/civilization_evolution.gd")
|
||||
var balanced: Dictionary = {"m": 10000.0, "l": 10000.0, "w": 10000.0, "d": 10000.0, "c": 10000.0, "b": 10000.0}
|
||||
var balanced: Dictionary = {"m": 10000, "l": 10000, "w": 10000, "d": 10000, "c": 10000, "b": 10000}
|
||||
var neutral: Dictionary = evolution.drift({}, "test", balanced, global.CivilizationRules["C2.0"].ethics)
|
||||
var diplomatic: Dictionary = evolution.drift({}, "test", balanced, global.CivilizationRules["C2.0"].ethics, true)
|
||||
check(neutral == balanced, "Ordinary drift preserves balanced ethics")
|
||||
check(diplomatic.d > diplomatic.w, "Diplomacy uses per-ethic influence")
|
||||
check(evolution.total(diplomatic) == 60000.0, "Diplomacy conserves population")
|
||||
check(E.eq(neutral, balanced), "Ordinary drift preserves balanced ethics")
|
||||
check(E.gt(diplomatic.d, diplomatic.w), "Diplomacy uses per-ethic influence")
|
||||
check(E.eq(evolution.total(diplomatic), 60000), "Diplomacy conserves population")
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1e15
|
||||
world.state.amount.BG = 1e15
|
||||
world.state.amount["C3.0"] = 6.0
|
||||
world.state.amount.RD = E.of("1e15")
|
||||
world.state.amount.BG = E.of("1e15")
|
||||
world.state.amount["C3.0"] = 6
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.amount["C3.0"] == 0.0 and world.state.amount["C2.9"] == 6.0, "Missing mandatory structures degrade existing civilizations")
|
||||
check((E.eq(world.state.amount["C3.0"], 0) and E.eq(world.state.amount["C2.9"], 6)), "Missing mandatory structures degrade existing civilizations")
|
||||
invariants(world)
|
||||
dispose(world)
|
||||
print("Civilization evolution: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Civilization evolution: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -38,97 +39,97 @@ func dispose(world) -> void:
|
||||
func run() -> void:
|
||||
var world = fixture()
|
||||
var global: Node = root.get_node("Global")
|
||||
check(global.CivilizationRules.size() == 44, "Load all ethical civilization stages")
|
||||
for row: Dictionary in global.CivilizationRules.values():
|
||||
check(row.ethics.size() == 6 or row.span == 0.0, "Every ethical stage has six rules")
|
||||
world.state.amount.RD = 12.0
|
||||
world.state.amount["C0.2"] = 8.0
|
||||
world.state.binds.RD = {"C2.0": 12.0, "C1.9": 3.0}
|
||||
check(E.eq(global.CivilizationRules.size(), 44), "Load all ethical civilization stages")
|
||||
for row : Dictionary in global.CivilizationRules.values():
|
||||
check((E.eq(row.ethics.size(), 6) or E.eq(row.span, 0)), "Every ethical stage has six rules")
|
||||
world.state.amount.RD = 12
|
||||
world.state.amount["C0.2"] = 8
|
||||
world.state.binds.RD = {"C2.0": 12, "C1.9": 3}
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.amount["C2.0"] == 12.0 and world.state.binds.RD["C2.0"] == 0.0, "Old space civilizations detach without loss")
|
||||
check(world.state.binds.RD["C1.9"] == 3.0, "Planetary civilizations retain their host")
|
||||
check(world.state.ethics["C0.2"] == {"m": 2.0, "l": 2.0, "w": 1.0, "d": 1.0, "c": 1.0, "b": 1.0}, "Migration uses equal ethics and deterministic remainder")
|
||||
check(world.state.ethics["C2.0"].values().all(func(n): return n == 2.0), "Bound civilization ethics count physical population")
|
||||
check((E.eq(world.state.amount["C2.0"], 12) and E.eq(world.state.binds.RD["C2.0"], 0)), "Old space civilizations detach without loss")
|
||||
check(E.eq(world.state.binds.RD["C1.9"], 3), "Planetary civilizations retain their host")
|
||||
check(E.eq(world.state.ethics["C0.2"], {"m": 2, "l": 2, "w": 1, "d": 1, "c": 1, "b": 1}), "Migration uses equal ethics and deterministic remainder")
|
||||
check(world.state.ethics["C2.0"].values().all(func ( n ) : return E.eq(n, 2)), "Bound civilization ethics count physical population")
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state == before, "Repeated initialization preserves ethics")
|
||||
world.state.ethics["C0.2"] = {"m": 8.0, "l": 0.0, "w": 0.0, "d": 0.0, "c": 0.0, "b": 0.0}
|
||||
check(E.eq(world.state, before), "Repeated initialization preserves ethics")
|
||||
world.state.ethics["C0.2"] = {"m": 8, "l": 0, "w": 0, "d": 0, "c": 0, "b": 0}
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.ethics["C0.2"].m == 8.0, "Existing unequal distribution is retained")
|
||||
world.state.amount["C0.2"] = 3.0
|
||||
check(E.eq(world.state.ethics["C0.2"].m, 8), "Existing unequal distribution is retained")
|
||||
world.state.amount["C0.2"] = 3
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.ethics["C0.2"].m == 3.0, "Losses reduce ethics without inventing population")
|
||||
check(root.get_node("SaveManager").validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Ethics state validates")
|
||||
check(E.eq(world.state.ethics["C0.2"].m, 3), "Losses reduce ethics without inventing population")
|
||||
check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Ethics state validates")
|
||||
var restored = fixture()
|
||||
restored.state = bytes_to_var(var_to_bytes(world.state))
|
||||
restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
restored.evolution.initialize_life_batches()
|
||||
check(restored.state == world.state, "Save roundtrip preserves ethics and ownership")
|
||||
check(E.eq(restored.state, world.state), "Save roundtrip preserves ethics and ownership")
|
||||
dispose(restored)
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
world.state.amount.RD = 10.0
|
||||
for row: Dictionary in global.CivilizationRules["C1.9"].ethics.values():
|
||||
world.state.amount.RD = 10
|
||||
for row : Dictionary in global.CivilizationRules["C1.9"].ethics.values():
|
||||
row.progress = "1"
|
||||
row.degradation = "0"
|
||||
row.colonization = "0"
|
||||
world.state.binds.RD = {"C1.9": 10.0}
|
||||
world.state.batches["C1.9"] = [{"amount": 10.0, "start_year": 1.0, "end_year": 1.0}]
|
||||
world.evolution.evolve_batches(world.state, 1.0)
|
||||
check(world.state.amount["C2.0"] == 10.0 and world.state.binds.RD.get("C2.0", 0.0) == 0.0, "Space successor does not inherit planetary binding")
|
||||
world.state.binds.RD = {"C1.9": 10}
|
||||
world.state.batches["C1.9"] = [{"amount": 10, "start_year": 1, "end_year": 1}]
|
||||
world.evolution.evolve_batches(world.state, 1)
|
||||
check((E.eq(world.state.amount["C2.0"], 10) and E.eq(world.state.binds.RD.get("C2.0", 0), 0)), "Space successor does not inherit planetary binding")
|
||||
dispose(world)
|
||||
var capacity = load("res://world/owniverse/civilization_state.gd")
|
||||
world = fixture()
|
||||
world.state.amount.RD = 10.0
|
||||
world.state.amount["C2.0"] = 8.0
|
||||
world.state.amount.RD = 10
|
||||
world.state.amount["C2.0"] = 8
|
||||
var budget: Dictionary = capacity.stellar_budget(world, world.state, global.CivilizationRules)
|
||||
check(capacity.admit_transition(budget, global.CivilizationRules, "C1.9", "C2.0", 5.0) == 2.0, "Growth consumes only remaining stellar capacity")
|
||||
check(capacity.admit_transition(budget, global.CivilizationRules, "C1.9", "C2.0", 5.0) == 0.0, "A reservation cannot be spent twice")
|
||||
world.state.amount["C2.0"] = 1.0
|
||||
check(E.eq(capacity.admit_transition(budget, global.CivilizationRules, "C1.9", "C2.0", 5), 2), "Growth consumes only remaining stellar capacity")
|
||||
check(E.eq(capacity.admit_transition(budget, global.CivilizationRules, "C1.9", "C2.0", 5), 0), "A reservation cannot be spent twice")
|
||||
world.state.amount["C2.0"] = 1
|
||||
budget = capacity.stellar_budget(world, world.state, global.CivilizationRules)
|
||||
check(capacity.admit_transition(budget, global.CivilizationRules, "C2.0", "C2.1", 1.0) == 1.0, "A transition reuses the source reservation")
|
||||
check(budget.free.DwStar == 0.0, "C2.1 reserves ten dwarf stars")
|
||||
world.state.amount["C2.0"] = 0.0
|
||||
world.state.binds.SC = {"RD": 10.0}
|
||||
check(E.eq(capacity.admit_transition(budget, global.CivilizationRules, "C2.0", "C2.1", 1), 1), "A transition reuses the source reservation")
|
||||
check(E.eq(budget.free.DwStar, 0), "C2.1 reserves ten dwarf stars")
|
||||
world.state.amount["C2.0"] = 0
|
||||
world.state.binds.SC = {"RD": 10}
|
||||
budget = capacity.stellar_budget(world, world.state, global.CivilizationRules)
|
||||
check(budget.capacity.DwStar == 20.0, "Stars inside structures are counted once")
|
||||
check(E.eq(budget.capacity.DwStar, 20), "Stars inside structures are counted once")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 12.0
|
||||
world.state.amount["C2.1"] = 2.0
|
||||
world.state.amount["C2.0"] = 5.0
|
||||
world.state.amount.RD = 12
|
||||
world.state.amount["C2.1"] = 2
|
||||
world.state.amount["C2.0"] = 5
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.amount["C2.1"] == 1.0 and world.state.amount["C2.0"] == 2.0, "Shared budget supports advanced civilizations first")
|
||||
check(world.state.binds.RD["C1.9"] == 4.0, "Excess cascades to supported planetary stage")
|
||||
check((E.eq(world.state.amount["C2.1"], 1) and E.eq(world.state.amount["C2.0"], 2)), "Shared budget supports advanced civilizations first")
|
||||
check(E.eq(world.state.binds.RD["C1.9"], 4), "Excess cascades to supported planetary stage")
|
||||
var snapshot: Dictionary = world.state.duplicate(true)
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state == snapshot, "Capacity enforcement is idempotent")
|
||||
world.state.amount.RD = 0.0
|
||||
check(E.eq(world.state, snapshot), "Capacity enforcement is idempotent")
|
||||
world.state.amount.RD = 0
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.amount["C2.1"] == 0.0 and world.state.amount["C2.0"] == 0.0, "Vanished stars force all unsupported space civilizations down")
|
||||
check(world.ledger.physical_amounts(world.state)["C1.9"] == 7.0, "Forced degradation conserves civilizations")
|
||||
check((E.eq(world.state.amount["C2.1"], 0) and E.eq(world.state.amount["C2.0"], 0)), "Vanished stars force all unsupported space civilizations down")
|
||||
check(E.eq(world.ledger.physical_amounts(world.state)["C1.9"], 7), "Forced degradation conserves civilizations")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.LGW = 20.0
|
||||
world.state.amount.SMBH = 2.0e22
|
||||
world.state.amount["C4.2"] = 3.0
|
||||
world.state.amount.LGW = 20
|
||||
world.state.amount.SMBH = E.of("2.0e22")
|
||||
world.state.amount["C4.2"] = 3
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.amount["C4.2"] == 2.0, "SMBH capacity uses stellar mass equivalent")
|
||||
check(world.ledger.physical_amounts(world.state)["C1.9"] == 1.0, "Unsupported civilization degrades through missing habitat families")
|
||||
check(root.get_node("SaveManager").validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Capacity-limited state validates")
|
||||
check(E.eq(world.state.amount["C4.2"], 2), "SMBH capacity uses stellar mass equivalent")
|
||||
check(E.eq(world.ledger.physical_amounts(world.state)["C1.9"], 1), "Unsupported civilization degrades through missing habitat families")
|
||||
check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Capacity-limited state validates")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 2.0
|
||||
for row: Dictionary in global.CivilizationRules["C1.9"].ethics.values():
|
||||
world.state.amount.RD = 2
|
||||
for row : Dictionary in global.CivilizationRules["C1.9"].ethics.values():
|
||||
row.progress = "1"
|
||||
row.degradation = "0"
|
||||
row.colonization = "0"
|
||||
world.state.binds.RD = {"C1.9": 5.0}
|
||||
world.state.batches["C1.9"] = [{"amount": 5.0, "start_year": 1.0, "end_year": 1.0}]
|
||||
world.evolution.evolve_batches(world.state, 1.0)
|
||||
check(world.state.amount["C2.0"] == 2.0 and world.state.binds.RD["C1.9"] == 3.0, "Unfunded transition retains the original planetary civilization")
|
||||
world.state.binds.RD = {"C1.9": 5}
|
||||
world.state.batches["C1.9"] = [{"amount": 5, "start_year": 1, "end_year": 1}]
|
||||
world.evolution.evolve_batches(world.state, 1)
|
||||
check((E.eq(world.state.amount["C2.0"], 2) and E.eq(world.state.binds.RD["C1.9"], 3)), "Unfunded transition retains the original planetary civilization")
|
||||
dispose(world)
|
||||
print("Civilization state: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Civilization state: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+39
-38
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -39,39 +40,39 @@ func run() -> void:
|
||||
var global: Node = root.get_node("Global")
|
||||
var description = load("res://world/owniverse/item_description.gd")
|
||||
var world = fixture()
|
||||
world.state.amount.H = 8.0
|
||||
world.state.amount.He = 2.0
|
||||
world.state.amount.H = 8
|
||||
world.state.amount.He = 2
|
||||
for locale in ["en", "ru"]:
|
||||
global.Locale = locale
|
||||
var text: String = description.render(world, global, "RD")
|
||||
check(text.contains("[b]") and not text.contains("%value"), "Localized template resolved: " + locale)
|
||||
check(text.contains("#40cc40"), "Affordable cost green: " + locale)
|
||||
check(text.contains("4.0000E+10"), "Lifespan substituted: " + locale)
|
||||
check((text.contains("[b]") and (not text.contains("%value"))), E.add("Localized template resolved: ", locale))
|
||||
check(text.contains("#40cc40"), E.add("Affordable cost green: ", locale))
|
||||
check(text.contains("4.0000E+10"), E.add("Lifespan substituted: ", locale))
|
||||
text = description.render(world, global, "He")
|
||||
check(text.contains("He: 20.00% / H: 80.00%") and not text.contains("%value"), "Helium metadata and mixture: " + locale)
|
||||
check((text.contains("He: 20.00% / H: 80.00%") and (not text.contains("%value"))), E.add("Helium metadata and mixture: ", locale))
|
||||
text = description.render(world, global, "Shovelin", "Force")
|
||||
check(text.count("]—[/color]") == 4, "Missing force levels use dash: " + locale)
|
||||
check(E.eq(text.count("]—[/color]"), 4), E.add("Missing force levels use dash: ", locale))
|
||||
text = description.render(world, global, "YD")
|
||||
check(text.contains("#ff6060"), "Insufficient cost red: " + locale)
|
||||
check(text.contains("#ff6060"), E.add("Insufficient cost red: ", locale))
|
||||
var reordered: Dictionary = {"text": "%value0% / %value1%", "value0": "lifespan", "value1": "amount"}
|
||||
check(global.render_template(reordered, {"amount": "12", "lifespan": "99"}) == "99 / 12", "Metadata chooses variables, not fixed argument positions")
|
||||
world.state.amount.RD = 2.0
|
||||
world.state.binds.G = {"RD": 3.0, "HP": 7.0}
|
||||
var values: Dictionary = description.variables(world, global, "RD", -2.0)
|
||||
check(values.amount.contains("2") and values.amount.contains("(5)") and values.amount.contains("#888888"), "Free and total counts with gray binding total")
|
||||
check(values.amount.contains("#ff6060") and values.amount.contains("−2.0"), "Real-time decrease red")
|
||||
check(E.eq(global.render_template(reordered, {"amount": "12", "lifespan": "99"}), "99 / 12"), "Metadata chooses variables, not fixed argument positions")
|
||||
world.state.amount.RD = 2
|
||||
world.state.binds.G = {"RD": 3, "HP": 7}
|
||||
var values: Dictionary = description.variables(world, global, "RD", E.neg(2))
|
||||
check(((values.amount.contains("2") and values.amount.contains("(5)")) and values.amount.contains("#888888")), "Free and total counts with gray binding total")
|
||||
check((values.amount.contains("#ff6060") and values.amount.contains("−2.0")), "Real-time decrease red")
|
||||
values = description.variables(world, global, "HP")
|
||||
check(values.amount == "7", "Planet description includes bound planets")
|
||||
world.state.amount.EON1 = 2.0
|
||||
world.state.binds.G.EON2 = 3.0
|
||||
check(E.eq(values.amount, "7"), "Planet description includes bound planets")
|
||||
world.state.amount.EON1 = 2
|
||||
world.state.binds.G.EON2 = 3
|
||||
var saved_state: Dictionary = world.state.duplicate(true)
|
||||
var counters: Dictionary = world.presentation.amounts(world.state, world.catalog)
|
||||
check(counters.HP == 7.0 and counters.LP == 5.0 and counters.Planet == 12.0, "Planet counters include bound objects and aggregate eons")
|
||||
check(counters.RD == 2.0, "Star counters retain available quantity")
|
||||
check(world.state == saved_state, "Reading counters does not mutate simulation")
|
||||
check(world.presentation.amounts(world.state, world.catalog, true).HP == 0.0, "Produced counters remain independent of current population")
|
||||
values = description.variables(world, global, "RD", 2.0)
|
||||
check(values.amount.contains("#40cc40") and values.amount.contains("+2.0"), "Increase green")
|
||||
check(((E.eq(counters.HP, 7) and E.eq(counters.LP, 5)) and E.eq(counters.Planet, 12)), "Planet counters include bound objects and aggregate eons")
|
||||
check(E.eq(counters.RD, 2), "Star counters retain available quantity")
|
||||
check(E.eq(world.state, saved_state), "Reading counters does not mutate simulation")
|
||||
check(E.eq(world.presentation.amounts(world.state, world.catalog, true).HP, 0), "Produced counters remain independent of current population")
|
||||
values = description.variables(world, global, "RD", 2)
|
||||
check((values.amount.contains("#40cc40") and values.amount.contains("+2.0")), "Increase green")
|
||||
var stage_script = load("res://stage/stage.gd")
|
||||
check(stage_script.can_instantiate(), "Stage integration compiles")
|
||||
var stage = stage_script.new()
|
||||
@@ -81,9 +82,9 @@ func run() -> void:
|
||||
stage.g_item_descriptions = [label]
|
||||
stage.description_targets[0] = {"id": "RD", "category": "Entity"}
|
||||
stage._refresh_descriptions()
|
||||
check(label.bbcode_enabled and label.text.contains("Красный карлик"), "Stage displays localized rich text")
|
||||
check((label.bbcode_enabled and label.text.contains("Красный карлик")), "Stage displays localized rich text")
|
||||
stage._update_descriptions(0.25)
|
||||
world.state.amount.RD += 1.0
|
||||
world.state.amount.RD = E.add(world.state.amount.RD, 1)
|
||||
stage._update_descriptions(0.25)
|
||||
check(label.text.contains("+4.0"), "Selected description refreshes with rate per real second")
|
||||
global.Locale = "en"
|
||||
@@ -92,5 +93,5 @@ func run() -> void:
|
||||
label.free()
|
||||
stage.free()
|
||||
dispose(world)
|
||||
print("Descriptions: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Descriptions: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
extends SceneTree
|
||||
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Q = preload("res://world/owniverse/quantity_math.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func _initialize():
|
||||
call_deferred("run")
|
||||
|
||||
func check(actual, expected: String, label: String):
|
||||
checks += 1
|
||||
if str(actual) != expected:
|
||||
failures += 1
|
||||
push_error(label + ": " + str(actual) + " != " + expected)
|
||||
|
||||
func run():
|
||||
# Expected strings were generated independently with Python fractions.Fraction.
|
||||
var cases = JSON.parse_string(FileAccess.get_file_as_string("res://tests/fixtures/exact_oracle.json"))
|
||||
for row in cases:
|
||||
var a = E.of(row[0])
|
||||
var b = E.of(row[1])
|
||||
check(E.add(a, b), row[2], "sum")
|
||||
check(E.subtract(a, b), row[3], "difference")
|
||||
check(E.multiply(a, b), row[4], "product")
|
||||
check(E.divide(a, b), row[5], "quotient")
|
||||
check(E.floor_value(E.divide(a, b)), row[6], "floor")
|
||||
for text in ["9223372036854775807", "-9223372036854775808", "9007199254740993", "1000000000000000000000000001", "-1/10000000000000"]:
|
||||
var value = E.of(text)
|
||||
check(E.decode(JSON.parse_string(JSON.stringify(E.encode(value)))), text, "JSON round trip")
|
||||
check(E.add(E.of("9223372036854775807"), 1), "9223372036854775808", "int64 overflow promoted")
|
||||
check(E.subtract(E.of("-9223372036854775808"), 1), "-9223372036854775809", "int64 underflow promoted")
|
||||
check(Q.production_step([E.of("1e27")]), "1", "large integral recipe")
|
||||
check(Q.production_step([E.of("1e-13")]), "10000000000000", "tiny fractional recipe")
|
||||
var count = E.of("1000000000000000000000000001")
|
||||
var allocation = Q.allocate_whole({"a": count, "b": count, "c": count}, count)
|
||||
check(Q.sum(allocation), str(count), "allocation conserves every unit")
|
||||
check(allocation.a, "333333333333333333333333334", "deterministic remainder")
|
||||
var clock = Clock.advance({"datetime": E.of("1e27")}, 0.000000001, 1)
|
||||
check(E.subtract(clock.datetime, E.of("1e27")), "1/1000000000", "single tick at huge age")
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
||||
for id in world.catalog.entities:
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
var group: String = world.catalog.entities[id].group_as
|
||||
if group != "":
|
||||
world.state.amount[group] = 0
|
||||
world.state.stats.total_amount[group] = 0
|
||||
world.production.produce_hs("H", 1)
|
||||
check(world.state.energy, "999999999999999999999999999", "spend one energy")
|
||||
var life = world.life.life_products(world.state, world.catalog.entities.EON5, count)
|
||||
check(Q.sum(life), str(count), "life conservation at 1e27")
|
||||
var precise = E.divide(count, E.of("100000000000000000000000000000000000003"))
|
||||
var bounded = E.bounded(precise)
|
||||
check(E.lt(E.absolute(E.subtract(precise, bounded)), E.of("1e-36")), "true", "bounded fractional error")
|
||||
check(E.bounded(count), str(count), "integer counts never quantized")
|
||||
world.state.amount.S = E.of("1e24")
|
||||
world.state.amount.H = E.of("1e24")
|
||||
world.state.amount.RD = 1234567
|
||||
world.state.amount.YD = 765432
|
||||
world.state.amount.STN = 654321
|
||||
world.speed = 6
|
||||
var started: int = Time.get_ticks_msec()
|
||||
for i in 100:
|
||||
world.state.amount.RD = E.add(world.state.amount.RD, i + 1)
|
||||
world.evolute(0.1)
|
||||
check(str(world.state.auto_star_deficits).length() < 500, "true", "fraction storage stays bounded")
|
||||
print("100 changing-composition cycles: %d ms" % (Time.get_ticks_msec() - started))
|
||||
var saves = root.get_node("SaveManager")
|
||||
check(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), "", "long-running state saves")
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
print("Exact math: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://wxhre4pgx1lb
|
||||
+20
-19
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -40,19 +41,19 @@ func run() -> void:
|
||||
for id in world.catalog.entities:
|
||||
var expected: Array = []
|
||||
for key in world.catalog.entities:
|
||||
if world.catalog.entities[key].group_as == id:
|
||||
if E.eq(world.catalog.entities[key].group_as, id):
|
||||
expected.append(key)
|
||||
check(world.catalog.is_group(id) == (not expected.is_empty()), "Group classification: " + id)
|
||||
check(world.catalog.members(id) == (expected if not expected.is_empty() else [id]), "Group members: " + id)
|
||||
check(E.eq(world.catalog.is_group(id), ((not expected.is_empty()))), E.add("Group classification: ", id))
|
||||
check(E.eq(world.catalog.members(id), ((expected if (not expected.is_empty()) else [id]))), E.add("Group members: ", id))
|
||||
var members: Array = world.catalog.members("Star")
|
||||
members.clear()
|
||||
check(not world.catalog.members("Star").is_empty(), "Returned members are independent")
|
||||
check((not world.catalog.members("Star").is_empty()), "Returned members are independent")
|
||||
world.catalog.entities.N.group_as = "TestGroup"
|
||||
world.catalog.rebuild_group_index()
|
||||
check(world.catalog.members("TestGroup") == ["N"], "Explicit definition reload")
|
||||
check(E.eq(world.catalog.members("TestGroup"), ["N"]), "Explicit definition reload")
|
||||
world.catalog.entities.N.free()
|
||||
world.catalog.entities.erase("N")
|
||||
check(not world.catalog.is_group("TestGroup"), "Removed definition invalidates index")
|
||||
print("Group index: %d checks, %d failures" % [checks, failures])
|
||||
check((not world.catalog.is_group("TestGroup")), "Removed definition invalidates index")
|
||||
print(("Group index: %d checks, %d failures" % [checks, failures]))
|
||||
dispose(world)
|
||||
quit(1 if failures else 0)
|
||||
quit((1 if failures else 0))
|
||||
|
||||
+65
-64
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -37,87 +38,87 @@ func dispose(world) -> void:
|
||||
|
||||
func run() -> void:
|
||||
var world = fixture()
|
||||
world.state.amount.N = 0.0
|
||||
world.state.amount.HP = 40000.0
|
||||
world.state.amount.N = 0
|
||||
world.state.amount.HP = 40000
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.batches.HP.size() == 1, "Old HP receives life cohort")
|
||||
check(world.state.batches.HP[0].start_year == 5e6, "HP uses source span")
|
||||
check(E.eq(world.state.batches.HP.size(), 1), "Old HP receives life cohort")
|
||||
check(E.eq(world.state.batches.HP[0].start_year, 5000000), "HP uses source span")
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state == before, "Initialization is idempotent")
|
||||
world.evolution.evolve_batches(world.state, 5e6 + 5e6 / 40.0)
|
||||
check(world.state.amount.EON1 == 10.0, "HP abiogenesis follows source probability")
|
||||
check(world.state.amount.RP == 100.0, "HP degradation produces RP")
|
||||
check(world.state.amount.HP == 39890.0, "Unchanged planets retry, not disappear")
|
||||
check(world.state.stats.total_amount.HP == 0.0, "Retry does not inflate creation statistics")
|
||||
check(world.state.batches.HP.size() > 0, "Survivors get next attempt")
|
||||
check(E.eq(world.state, before), "Initialization is idempotent")
|
||||
world.evolution.evolve_batches(world.state, E.add(5000000, E.divide(5000000, 40)))
|
||||
check(E.eq(world.state.amount.EON1, 10), "HP abiogenesis follows source probability")
|
||||
check(E.eq(world.state.amount.RP, 100), "HP degradation produces RP")
|
||||
check(E.eq(world.state.amount.HP, 39890), "Unchanged planets retry, not disappear")
|
||||
check(E.eq(world.state.stats.total_amount.HP, 0), "Retry does not inflate creation statistics")
|
||||
check(E.gt(world.state.batches.HP.size(), 0), "Survivors get next attempt")
|
||||
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
check(physical.HP + physical.EON1 + physical.RP == 40000.0, "Life conserves planets")
|
||||
check(root.get_node("SaveManager").validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Life save validates")
|
||||
check(E.eq(E.add(E.add(physical.HP, physical.EON1), physical.RP), 40000), "Life conserves planets")
|
||||
check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Life save validates")
|
||||
var restored = fixture()
|
||||
restored.state = bytes_to_var(var_to_bytes(world.state))
|
||||
world.evolution.evolve_batches(world.state, 11e6)
|
||||
restored.evolution.evolve_batches(restored.state, 11e6)
|
||||
check(world.state == restored.state, "Life continuation preserves cohorts and carries")
|
||||
restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
world.evolution.evolve_batches(world.state, 11000000)
|
||||
restored.evolution.evolve_batches(restored.state, 11000000)
|
||||
check(E.eq(world.state, restored.state), "Life continuation preserves cohorts and carries")
|
||||
dispose(restored)
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1.0
|
||||
world.state.binds.RD = {"EON5": 40000.0}
|
||||
world.state.amount.RD = 1
|
||||
world.state.binds.RD = {"EON5": 40000}
|
||||
world.evolution.initialize_life_batches()
|
||||
world.evolution.evolve_batches(world.state, 5e5 + 5e5 / 40.0)
|
||||
check(world.state.binds.RD["C0.0"] == 10.0, "Intelligence emerges at the same host star")
|
||||
check(world.state.amount["C0.0"] == 0.0, "Life transition keeps planet bound")
|
||||
world.evolution.evolve_batches(world.state, E.add(500000, E.divide(500000, 40)))
|
||||
check(E.eq(world.state.binds.RD["C0.0"], 10), "Intelligence emerges at the same host star")
|
||||
check(E.eq(world.state.amount["C0.0"], 0), "Life transition keeps planet bound")
|
||||
check(world.state.batches.has("C0.0"), "New C0.0 starts civilization evolution")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
var progress: float = 0.0
|
||||
var degraded: float = 0.0
|
||||
var progress: Variant = 0
|
||||
var degraded: Variant = 0
|
||||
var valid_outcomes: bool = true
|
||||
for i in 4001:
|
||||
var products: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1.0)
|
||||
var total: float = 0.0
|
||||
var products: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1)
|
||||
var total: Variant = 0
|
||||
for value in products.values():
|
||||
total += value
|
||||
valid_outcomes = valid_outcomes and value >= 0.0 and value == floorf(value)
|
||||
valid_outcomes = valid_outcomes and total == 1.0
|
||||
progress += products.get("EON1", 0.0)
|
||||
degraded += products.get("RP", 0.0)
|
||||
total = E.add(total, value)
|
||||
valid_outcomes = ((valid_outcomes and E.ge(value, 0)) and E.eq(value, E.floor_value(value)))
|
||||
valid_outcomes = (valid_outcomes and E.eq(total, 1))
|
||||
progress = E.add(progress, products.get("EON1", 0))
|
||||
degraded = E.add(degraded, products.get("RP", 0))
|
||||
check(valid_outcomes, "Every single-planet attempt has whole outcomes and conserves population")
|
||||
check(progress == 1.0 and degraded == 10.0, "Rare outcomes accumulate deterministically")
|
||||
check((E.eq(progress, 1) and E.eq(degraded, 10)), "Rare outcomes accumulate deterministically")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.time = {"datetime": 123.4, "year": 123.4}
|
||||
world.state.binds.RD = {"HP": 2.0}
|
||||
world.state.time = {"datetime": E.of("123.4"), "year": E.of("123.4")}
|
||||
world.state.binds.RD = {"HP": 2}
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.batches.HP[0].created_from == 123.4, "Old bound worlds begin life at load time")
|
||||
world.stellar.destroy_planets(world.state, "RD", 1.0, 1)
|
||||
world.evolution.evolve_batches(world.state, 1e7)
|
||||
check(world.ledger.physical_amounts(world.state).HP == 0.0 and world.state.binds.RD.RP == 2.0, "Destroyed life does not resurrect from cohort")
|
||||
check(E.eq(world.state.batches.HP[0].created_from, E.of("123.4")), "Old bound worlds begin life at load time")
|
||||
world.stellar.destroy_planets(world.state, "RD", 1, 1)
|
||||
world.evolution.evolve_batches(world.state, 10000000)
|
||||
check((E.eq(world.ledger.physical_amounts(world.state).HP, 0) and E.eq(world.state.binds.RD.RP, 2)), "Destroyed life does not resurrect from cohort")
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
world.state.time = {"datetime": 123.4, "year": 123.4}
|
||||
world.state.amount["C0.0"] = 40000.0
|
||||
world.state.time = {"datetime": E.of("123.4"), "year": E.of("123.4")}
|
||||
world.state.amount["C0.0"] = 40000
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state.batches["C0.0"][0].created_from == 123.4, "Civilization cohorts begin at the current time")
|
||||
check(E.eq(world.state.batches["C0.0"][0].created_from, E.of("123.4")), "Civilization cohorts begin at the current time")
|
||||
before = world.state.duplicate(true)
|
||||
world.evolution.initialize_life_batches()
|
||||
check(world.state == before, "Life batch initialization is idempotent")
|
||||
world.evolution.evolve_batches(world.state, 2173.4)
|
||||
check(world.state.amount["C0.1"] == 700.0 and world.state.amount.EON5 == 1000.0, "C0.0 progresses and degrades by Unity rates")
|
||||
check(world.state.amount["C0.0"] == 38300.0, "C0.0 remainder stays alive")
|
||||
check(E.eq(world.state, before), "Life batch initialization is idempotent")
|
||||
world.evolution.evolve_batches(world.state, E.of("2173.4"))
|
||||
check((E.eq(world.state.amount["C0.1"], 700) and E.eq(world.state.amount.EON5, 1000)), "C0.0 progresses and degrades by Unity rates")
|
||||
check(E.eq(world.state.amount["C0.0"], 38300), "C0.0 remainder stays alive")
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1.0
|
||||
world.state.binds.RD = {"C0.1": 40000.0}
|
||||
world.state.amount.RD = 1
|
||||
world.state.binds.RD = {"C0.1": 40000}
|
||||
world.evolution.initialize_life_batches()
|
||||
world.evolution.evolve_batches(world.state, 2050.0)
|
||||
check(world.state.binds.RD["C0.2"] == 700.0, "C0.1 reaches ethical civilization without losing host")
|
||||
check(world.state.binds.RD["C0.0"] == 1000.0, "C0.1 degradation follows Unity")
|
||||
check(root.get_node("SaveManager").validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Extended life save validates")
|
||||
world.evolution.evolve_batches(world.state, 2050)
|
||||
check(E.eq(world.state.binds.RD["C0.2"], 700), "C0.1 reaches ethical civilization without losing host")
|
||||
check(E.eq(world.state.binds.RD["C0.0"], 1000), "C0.1 degradation follows Unity")
|
||||
check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Extended life save validates")
|
||||
dispose(world)
|
||||
print("Life: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Life: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+28
-27
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not value):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
@@ -21,48 +22,48 @@ func run() -> void:
|
||||
await process_frame
|
||||
await process_frame
|
||||
var world = stage.owniverse
|
||||
world.state.binds.RD = {"EON1": 10.0, "EON2": 4.0, "C0.0": 3.0}
|
||||
world.state.amount.EON1 = 2.0
|
||||
world.state.stats.total_amount.EON1 = 12.0
|
||||
world.state.stats.total_amount.EON2 = 4.0
|
||||
world.state.stats.total_amount["C0.0"] = 3.0
|
||||
world.state.binds.RD = {"EON1": 10, "EON2": 4, "C0.0": 3}
|
||||
world.state.amount.EON1 = 2
|
||||
world.state.stats.total_amount.EON1 = 12
|
||||
world.state.stats.total_amount.EON2 = 4
|
||||
world.state.stats.total_amount["C0.0"] = 3
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
stage._refresh_universe_ui()
|
||||
var counts: Dictionary = world.presentation.amounts(world.state, world.catalog)
|
||||
check(counts.EON1 == 12.0 and counts.EON2 == 4.0 and counts.LP == 16.0, "Individual eons and their total include bound planets")
|
||||
check(counts["C0.0"] == 3.0 and counts.C0 == 3.0 and counts.Planet == 19.0, "Civilization detail does not double the planetary total")
|
||||
check(world.presentation.amounts(world.state, world.catalog, true).EON1 == 12.0 and world.presentation.amounts(world.state, world.catalog, true).LP == 16.0, "Historical counts retain individual and aggregate values")
|
||||
check(world.state == before, "Presentation does not modify simulation state")
|
||||
check(((E.eq(counts.EON1, 12) and E.eq(counts.EON2, 4)) and E.eq(counts.LP, 16)), "Individual eons and their total include bound planets")
|
||||
check(((E.eq(counts["C0.0"], 3) and E.eq(counts.C0, 3)) and E.eq(counts.Planet, 19)), "Civilization detail does not double the planetary total")
|
||||
check((E.eq(world.presentation.amounts(world.state, world.catalog, true).EON1, 12) and E.eq(world.presentation.amounts(world.state, world.catalog, true).LP, 16)), "Historical counts retain individual and aggregate values")
|
||||
check(E.eq(world.state, before), "Presentation does not modify simulation state")
|
||||
var pending: Array = ["EON1", "EON2", "EON3", "C0.0"]
|
||||
var pending_before: Array = pending.duplicate()
|
||||
var decision: Dictionary = world.presentation.unlocks(world.state, world.catalog, pending)
|
||||
check(decision.remaining == ["EON3"], "Only unmet requirements remain pending")
|
||||
check(E.eq(decision.remaining, ["EON3"]), "Only unmet requirements remain pending")
|
||||
for id in ["EON1", "EON2", "LP", "C0.0", "C0"]:
|
||||
check(decision.visible.has(id), "Unlock projection includes detail and alias: " + id)
|
||||
check(pending == pending_before and world.state == before, "Unlock evaluation leaves inputs untouched")
|
||||
check(decision.visible.has(id), E.add("Unlock projection includes detail and alias: ", id))
|
||||
check((E.eq(pending, pending_before) and E.eq(world.state, before)), "Unlock evaluation leaves inputs untouched")
|
||||
var snapshot: Dictionary = world.presentation.pending_amounts.duplicate()
|
||||
world.presentation.deferred_updates = true
|
||||
world.state.amount.EON1 += 1.0
|
||||
world.state.amount.EON1 = E.add(world.state.amount.EON1, 1)
|
||||
world.presentation.refresh(world.state, world.catalog)
|
||||
check(world.presentation.dirty and world.presentation.pending_amounts == snapshot, "Deferred refresh retains pending snapshot")
|
||||
check((world.presentation.dirty and E.eq(world.presentation.pending_amounts, snapshot)), "Deferred refresh retains pending snapshot")
|
||||
world.presentation.refresh(world.state, world.catalog, true)
|
||||
check(not world.presentation.dirty and world.presentation.pending_amounts.EON1 == 13.0, "Forced refresh publishes latest amounts")
|
||||
world.state.amount.EON1 -= 1.0
|
||||
check(((not world.presentation.dirty) and E.eq(world.presentation.pending_amounts.EON1, 13)), "Forced refresh publishes latest amounts")
|
||||
world.state.amount.EON1 = E.subtract(world.state.amount.EON1, 1)
|
||||
world.presentation.deferred_updates = false
|
||||
world.presentation.refresh(world.state, world.catalog)
|
||||
for id in ["EON1", "EON2", "LP", "C0.0", "C0"]:
|
||||
check(stage.owniverse_items[id].visible, "Unlock both levels: " + id)
|
||||
check(stage.owniverse_items[id].description.text != "", "Populate the icon counter: " + id)
|
||||
check(not stage.owniverse_items.EON3.visible, "Unreached eon remains locked")
|
||||
check(stage.owniverse_items[id].visible, E.add("Unlock both levels: ", id))
|
||||
check(E.ne(stage.owniverse_items[id].description.text, ""), E.add("Populate the icon counter: ", id))
|
||||
check((not stage.owniverse_items.EON3.visible), "Unreached eon remains locked")
|
||||
stage.item_activated(stage.owniverse_items.LP)
|
||||
check(stage.owniverse_items.EON1.is_visible_in_tree(), "Selecting LP shows its unlocked eons")
|
||||
stage.item_activated(stage.owniverse_items.C0)
|
||||
check(stage.owniverse_items["C0.0"].is_visible_in_tree() and not stage.owniverse_items.EON1.is_visible_in_tree(), "Selecting C0 switches the detail panel")
|
||||
world.state.binds.RD.EON2 = 0.0
|
||||
check((stage.owniverse_items["C0.0"].is_visible_in_tree() and (not stage.owniverse_items.EON1.is_visible_in_tree())), "Selecting C0 switches the detail panel")
|
||||
world.state.binds.RD.EON2 = 0
|
||||
stage._refresh_universe_ui()
|
||||
check(stage.owniverse_items.EON2.visible and stage.owniverse_items.EON2.description.text == "", "Previously reached stage stays unlocked after extinction with no stale count")
|
||||
check((stage.owniverse_items.EON2.visible and E.eq(stage.owniverse_items.EON2.description.text, "")), "Previously reached stage stays unlocked after extinction with no stale count")
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
scene.free()
|
||||
print("Life icons: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Life icons: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
@@ -19,20 +20,20 @@ func run() -> void:
|
||||
var names: Array = ["batches", "ledger", "production", "automatic", "stellar", "life", "environment", "evolution"]
|
||||
var references: Array = []
|
||||
for name in names:
|
||||
check(first.get(name) != second.get(name), "World has its own " + name)
|
||||
check(first.get(name).world == first, "Module refers to its owner: " + name)
|
||||
check(E.ne(first.get(name), second.get(name)), E.add("World has its own ", name))
|
||||
check(E.eq(first.get(name).world, first), E.add("Module refers to its owner: ", name))
|
||||
references.append(weakref(first.get(name)))
|
||||
first.state.amount.H = 123.0
|
||||
first.presentation.pending_amounts.H = 123.0
|
||||
check(not second.state.amount.has("H") and second.presentation.pending_amounts.is_empty(), "State and pending presentation are isolated")
|
||||
first.state.amount.H = 123
|
||||
first.presentation.pending_amounts.H = 123
|
||||
check(((not second.state.amount.has("H")) and second.presentation.pending_amounts.is_empty()), "State and pending presentation are isolated")
|
||||
first.free()
|
||||
for reference in references:
|
||||
check(reference.get_ref() == null, "Module is released with its world")
|
||||
check(second.evolution.world == second, "Other world remains usable")
|
||||
check(E.eq(reference.get_ref(), null), "Module is released with its world")
|
||||
check(E.eq(second.evolution.world, second), "Other world remains usable")
|
||||
var snapshot: Dictionary = second.state.duplicate(true)
|
||||
second.evolute(NAN)
|
||||
second.evolute(-1.0)
|
||||
check(second.state == snapshot, "Entry point rejects invalid time deltas")
|
||||
second.evolute(E.neg(1))
|
||||
check(E.eq(second.state, snapshot), "Entry point rejects invalid time deltas")
|
||||
second.free()
|
||||
print("Module lifecycle: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
print(("Module lifecycle: %d checks, %d failures" % [checks, failures]))
|
||||
quit((1 if failures else 0))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -38,21 +39,21 @@ func dispose(world) -> void:
|
||||
func run() -> void:
|
||||
var world = fixture()
|
||||
world.evolution.initialize_life_batches()
|
||||
world.catalog.entities.N.cost = {"H": 2.0}
|
||||
world.catalog.entities.N.bind = {"S": 3.0}
|
||||
world.state.amount.H = 10.0
|
||||
world.state.amount.S = 0.0
|
||||
world.state.auto_resources = {"H": 10.0}
|
||||
world.state.batches.H = [{"amount": 10.0, "start_year": 1.0, "end_year": 2.0}]
|
||||
world.catalog.entities.N.cost = {"H": 2}
|
||||
world.catalog.entities.N.bind = {"S": 3}
|
||||
world.state.amount.H = 10
|
||||
world.state.amount.S = 0
|
||||
world.state.auto_resources = {"H": 10}
|
||||
world.state.batches.H = [{"amount": 10, "start_year": 1, "end_year": 2}]
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
check(not world.production.try_production(world.state, world.catalog.entities.N, 1.0, true), "Binding failure rejects production")
|
||||
check(world.state == before, "Failure preserves costs, batches and budget")
|
||||
world.state.amount.S = 10.0
|
||||
check((not world.production.try_production(world.state, world.catalog.entities.N, 1, true)), "Binding failure rejects production")
|
||||
check(E.eq(world.state, before), "Failure preserves costs, batches and budget")
|
||||
world.state.amount.S = 10
|
||||
var old_batches: Array = world.state.batches.H
|
||||
check(world.production.try_production(world.state, world.catalog.entities.N, 2.0, true), "Funded production succeeds")
|
||||
check(world.state.amount.H == 6.0 and world.state.amount.S == 4.0, "Exact cost and binding")
|
||||
check(world.state.auto_resources.H == 6.0, "Exact automatic budget")
|
||||
check(old_batches[0].amount == 10.0 and world.state.batches.H[0].amount == 6.0, "Consumed batches isolated")
|
||||
print("Transaction: %d checks, %d failures" % [checks, failures])
|
||||
check(world.production.try_production(world.state, world.catalog.entities.N, 2, true), "Funded production succeeds")
|
||||
check((E.eq(world.state.amount.H, 6) and E.eq(world.state.amount.S, 4)), "Exact cost and binding")
|
||||
check(E.eq(world.state.auto_resources.H, 6), "Exact automatic budget")
|
||||
check((E.eq(old_batches[0].amount, 10) and E.eq(world.state.batches.H[0].amount, 6)), "Consumed batches isolated")
|
||||
print(("Transaction: %d checks, %d failures" % [checks, failures]))
|
||||
dispose(world)
|
||||
quit(1 if failures else 0)
|
||||
quit((1 if failures else 0))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
const Math = preload("res://world/owniverse/quantity_math.gd")
|
||||
var checks: int = 0
|
||||
@@ -8,56 +9,56 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
var Catalog = load("res://world/owniverse/entity_catalog.gd")
|
||||
check(Math.allocate_whole({}, 10.0) == {}, "Empty allocation")
|
||||
check(Math.allocate_whole({"b": 1.0, "a": 1.0}, 1.0) == {"a": 1.0, "b": 0.0}, "Stable lexical tie")
|
||||
check(E.eq(Math.allocate_whole({}, 10), {}), "Empty allocation")
|
||||
check(E.eq(Math.allocate_whole({"b": 1, "a": 1}, 1), {"a": 1, "b": 0}), "Stable lexical tie")
|
||||
for limit in range(30):
|
||||
var requests: Dictionary = {"a": 3.9, "b": 9.1, "c": -2.0, "d": 7.0}
|
||||
var requests: Dictionary = {"a": E.of("3.9"), "b": E.of("9.1"), "c": E.neg(2), "d": 7}
|
||||
var result: Dictionary = Math.allocate_whole(requests, limit)
|
||||
check(Math.sum(result) == minf(limit, 19.0), "Conserve bounded allocation")
|
||||
check(E.eq(Math.sum(result), E.minimum(limit, 19)), "Conserve bounded allocation")
|
||||
for id in result:
|
||||
check(result[id] == floorf(result[id]) and result[id] >= 0.0 and result[id] <= floorf(maxf(0.0, requests[id])), "Whole bounded branch")
|
||||
check(Math.normalize_distribution({"a": -1.0, "b": 0.0}) == {}, "Nonpositive weights")
|
||||
check(Math.normalize_distribution({"a": 2.0, "b": 6.0}) == {"a": 0.25, "b": 0.75}, "Normalize weights")
|
||||
check(Math.production_step([0.25, 0.1, 2.0]) == 20.0, "Common whole production step")
|
||||
check(Math.production_step([]) == 1.0, "Free production step")
|
||||
check(((E.eq(result[id], E.floor_value(result[id])) and E.ge(result[id], 0)) and E.le(result[id], E.floor_value(E.maximum(0, requests[id])))), "Whole bounded branch")
|
||||
check(E.eq(Math.normalize_distribution({"a": E.neg(1), "b": 0}), {}), "Nonpositive weights")
|
||||
check(E.eq(Math.normalize_distribution({"a": 2, "b": 6}), {"a": E.of("0.25"), "b": E.of("0.75")}), "Normalize weights")
|
||||
check(E.eq(Math.production_step([E.of("0.25"), E.of("0.1"), 2]), 20), "Common whole production step")
|
||||
check(E.eq(Math.production_step([]), 1), "Free production step")
|
||||
var target: Dictionary = {}
|
||||
var produced: float = 0.0
|
||||
var produced: Variant = 0
|
||||
for i in 10:
|
||||
produced += Math.accumulate_whole(target, "remainders", "a", 0.1)
|
||||
check(produced == 1.0, "Fractional production carries")
|
||||
produced = E.add(produced, Math.accumulate_whole(target, "remainders", "a", E.of("0.1")))
|
||||
check(E.eq(produced, 1), "Fractional production carries")
|
||||
var remainders: Dictionary = {}
|
||||
var totals: Dictionary = {"a": 0.0, "b": 0.0, "c": 0.0}
|
||||
var totals: Dictionary = {"a": 0, "b": 0, "c": 0}
|
||||
for i in 30:
|
||||
var result: Dictionary = Math.split(remainders, "split", 1.0, {"a": 1.0, "b": 1.0, "c": 1.0})
|
||||
check(Math.sum(result) == 1.0, "Split conserves count")
|
||||
for id in result: totals[id] += result[id]
|
||||
check(totals == {"a": 10.0, "b": 10.0, "c": 10.0}, "Rounding debt distributes over time")
|
||||
produced = 0.0
|
||||
for i in 10: produced += Math.accumulate_rate(remainders, "rate", 1.0, 0.1)
|
||||
check(produced == 1.0, "Rate carries")
|
||||
var result: Dictionary = Math.split(remainders, "split", 1, {"a": 1, "b": 1, "c": 1})
|
||||
check(E.eq(Math.sum(result), 1), "Split conserves count")
|
||||
for id in result: totals[id] = E.add(totals[id], result[id])
|
||||
check(E.eq(totals, {"a": 10, "b": 10, "c": 10}), "Rounding debt distributes over time")
|
||||
produced = 0
|
||||
for i in 10: produced = E.add(produced, Math.accumulate_rate(remainders, "rate", 1, E.of("0.1")))
|
||||
check(E.eq(produced, 1), "Rate carries")
|
||||
var catalog = Catalog.new()
|
||||
var rows: Dictionary = root.get_node("Global").OwniverseEntities
|
||||
catalog.load_definitions(rows)
|
||||
check(catalog.entities.size() == rows.size(), "Catalog loads all definitions")
|
||||
check(E.eq(catalog.entities.size(), rows.size()), "Catalog loads all definitions")
|
||||
for id in catalog.entities:
|
||||
var entity = catalog.entities[id]
|
||||
check(catalog.automatic_producers.has(entity) == (entity.product_per_year_type != ""), "Producer index")
|
||||
check(catalog.unlockable_entities.has(entity) == (not entity.unlock.is_empty()), "Unlock index")
|
||||
if entity.group_as != "":
|
||||
check(catalog.groups[entity.group_as].has(id) and catalog.members(entity.group_as).has(id), "Group index")
|
||||
if entity.event_group != "":
|
||||
check(E.eq(catalog.automatic_producers.has(entity), (E.ne(entity.product_per_year_type, ""))), "Producer index")
|
||||
check(E.eq(catalog.unlockable_entities.has(entity), ((not entity.unlock.is_empty()))), "Unlock index")
|
||||
if E.ne(entity.group_as, ""):
|
||||
check((catalog.groups[entity.group_as].has(id) and catalog.members(entity.group_as).has(id)), "Group index")
|
||||
if E.ne(entity.event_group, ""):
|
||||
check(catalog.event_groups[entity.event_group].has(id), "Event index")
|
||||
var members: Array = catalog.members("Star")
|
||||
members.clear()
|
||||
check(not catalog.members("Star").is_empty(), "Group results are independent")
|
||||
check((not catalog.members("Star").is_empty()), "Group results are independent")
|
||||
check(Catalog.new().entities.is_empty(), "Catalog instances are independent")
|
||||
for entity in catalog.entities.values(): entity.free()
|
||||
print("Quantity and catalog: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
print(("Quantity and catalog: %d checks, %d failures" % [checks, failures]))
|
||||
quit((1 if failures else 0))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
@@ -6,20 +7,20 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture(legacy: bool):
|
||||
var world = load("res://tests/fixtures/legacy_stellar_events.gd" if legacy else "res://world/owniverse/owniverse.gd").new()
|
||||
var world = load(("res://tests/fixtures/legacy_stellar_events.gd" if legacy else "res://world/owniverse/owniverse.gd")).new()
|
||||
for id in root.get_node("Global").OwniverseEntities:
|
||||
world.catalog.entities[id] = load("res://world/owniverse/owniverse_entity.gd").new(root.get_node("Global").OwniverseEntities[id])
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if world.catalog.entities[id].group_as != "":
|
||||
world.state.amount[world.catalog.entities[id].group_as] = 0.0
|
||||
world.state.stats.total_amount[world.catalog.entities[id].group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(world.catalog.entities[id].group_as, ""):
|
||||
world.state.amount[world.catalog.entities[id].group_as] = 0
|
||||
world.state.stats.total_amount[world.catalog.entities[id].group_as] = 0
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -27,52 +28,52 @@ func dispose(world) -> void:
|
||||
world.free()
|
||||
|
||||
func equivalent(a: Variant, b: Variant) -> bool:
|
||||
if a is Dictionary and b is Dictionary:
|
||||
if a.size() != b.size(): return false
|
||||
if ((a is Dictionary) and (b is Dictionary)):
|
||||
if E.ne(a.size(), b.size()): return false
|
||||
for key in a:
|
||||
if not b.has(key) or not equivalent(a[key], b[key]): return false
|
||||
if ((not b.has(key)) or (not equivalent(a[key], b[key]))): return false
|
||||
return true
|
||||
if a is Array and b is Array:
|
||||
if a.size() != b.size(): return false
|
||||
if ((a is Array) and (b is Array)):
|
||||
if E.ne(a.size(), b.size()): return false
|
||||
for i in a.size():
|
||||
if not equivalent(a[i], b[i]): return false
|
||||
if (not equivalent(a[i], b[i])): return false
|
||||
return true
|
||||
if (a is float or a is int) and (b is float or b is int):
|
||||
return absf(float(a) - float(b)) < 1e-6
|
||||
return a == b
|
||||
if ((((a is float) or (a is int))) and (((b is float) or (b is int)))):
|
||||
return E.lt(E.absolute(E.subtract(E.of(a), E.of(b))), E.of("1e-6"))
|
||||
return E.eq(a, b)
|
||||
|
||||
func run() -> void:
|
||||
for parent in ["LRG", "HRG", "RSG", "RHG"]:
|
||||
for year in [101.0, 101.5, 104.75, 1767.3, 3005.0]:
|
||||
for year in [101, E.of("101.5"), E.of("104.75"), E.of("1767.3"), 3005]:
|
||||
var fast = fixture(false)
|
||||
var legacy = fixture(true)
|
||||
fast.state.amount[parent] = 20000.0
|
||||
fast.state.amount.RD = 100000.0
|
||||
fast.state.amount.HP = 50.0
|
||||
fast.state.amount.Met = 100.0
|
||||
fast.state.binds.RD = {"HP": 2000.0, "C0.1": 100.0}
|
||||
fast.state.binds[parent] = {"HP": 10.0}
|
||||
fast.state.batches[parent] = [{"amount": 20000.0, "start_year": 100.3, "end_year": 3000.7}]
|
||||
fast.state.amount[parent] = 20000
|
||||
fast.state.amount.RD = 100000
|
||||
fast.state.amount.HP = 50
|
||||
fast.state.amount.Met = 100
|
||||
fast.state.binds.RD = {"HP": 2000, "C0.1": 100}
|
||||
fast.state.binds[parent] = {"HP": 10}
|
||||
fast.state.batches[parent] = [{"amount": 20000, "start_year": E.of("100.3"), "end_year": E.of("3000.7")}]
|
||||
# Existing short-lived populations test integration with new arrivals.
|
||||
fast.state.amount.SN = 3.0
|
||||
fast.batches.append_batch(fast.state, "SN", 3.0, 100.5, 100.5)
|
||||
if year == 104.75: fast.state.energy = 7.0
|
||||
fast.state.amount.SN = 3
|
||||
fast.batches.append_batch(fast.state, "SN", 3, E.of("100.5"), E.of("100.5"))
|
||||
if E.eq(year, E.of("104.75")): fast.state.energy = 7
|
||||
legacy.state = fast.state.duplicate(true)
|
||||
fast.evolution.evolve_batches(fast.state, year)
|
||||
legacy.evolution.evolve_batches(legacy.state, year)
|
||||
for field in fast.state:
|
||||
check(equivalent(fast.state[field], legacy.state[field]), "%s at %s: %s matches materialized events" % [parent, year, field])
|
||||
check(equivalent(fast.state[field], legacy.state[field]), ("%s at %s: %s matches materialized events" % [parent, year, field]))
|
||||
for id in ["SN", "DSN", "BSN", "HN", "GRB", "SGRB", "LGRB", "HGRB"]:
|
||||
check(fast.state.batches.get(id, []).size() <= 3, "Only surviving event windows remain")
|
||||
check(E.le(fast.state.batches.get(id, []).size(), 3), "Only surviving event windows remain")
|
||||
dispose(fast)
|
||||
dispose(legacy)
|
||||
var fast = fixture(false)
|
||||
var pending: Dictionary = {}
|
||||
fast.batches.append_batch(fast.state, "SN", 10000.0, 0.25, 1667.25, pending, 1667.25)
|
||||
check(fast.state.batches.SN.size() <= 2 and pending.SN.size() == 1, "Completed windows aggregate without transient batch allocation")
|
||||
var total: float = pending.SN[0].amount
|
||||
for batch in fast.state.batches.SN: total += batch.amount
|
||||
check(total == 10000.0, "Direct completions plus survivors conserve event count")
|
||||
fast.batches.append_batch(fast.state, "SN", 10000, E.of("0.25"), E.of("1667.25"), pending, E.of("1667.25"))
|
||||
check((E.le(fast.state.batches.SN.size(), 2) and E.eq(pending.SN.size(), 1)), "Completed windows aggregate without transient batch allocation")
|
||||
var total: Variant = pending.SN[0].amount
|
||||
for batch in fast.state.batches.SN: total = E.add(total, batch.amount)
|
||||
check(E.eq(total, 10000), "Direct completions plus survivors conserve event count")
|
||||
dispose(fast)
|
||||
print("Short-lived events: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Short-lived events: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+155
-154
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
@@ -6,9 +7,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -17,16 +18,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -37,226 +38,226 @@ func dispose(world) -> void:
|
||||
func run() -> void:
|
||||
var world = fixture()
|
||||
for id in ["DSN", "SN", "BSN", "HN", "SGRB", "GRB", "LGRB", "HGRB"]:
|
||||
check(world.stellar.evolution_products(world.state, world.catalog.entities[id], 3.0).blast == 3.0 * world.catalog.entities[id].blast, "Entity blast determines power: " + id)
|
||||
world.catalog.entities.SN.output_and.blast = 999.0
|
||||
check(E.eq(world.stellar.evolution_products(world.state, world.catalog.entities[id], 3).blast, E.multiply(3, world.catalog.entities[id].blast)), E.add("Entity blast determines power: ", id))
|
||||
world.catalog.entities.SN.output_and.blast = 999
|
||||
world.catalog.entities.SN.blast = 4
|
||||
check(world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2.0).blast == 8.0, "Imported output cannot override or double blast power")
|
||||
check(E.eq(world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2).blast, 8), "Imported output cannot override or double blast power")
|
||||
world.catalog.entities.SN.blast = 0
|
||||
check(not world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2.0).has("blast"), "Zero entity blast disables external event")
|
||||
world.catalog.entities.SN.output_or = {"positive": "blast:999", "negative": "blast:999", "chance": 50.0}
|
||||
check(not world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2.0).has("blast"), "Alternative output cannot restore disabled blast")
|
||||
check((not world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2).has("blast")), "Zero entity blast disables external event")
|
||||
world.catalog.entities.SN.output_or = {"positive": "blast:999", "negative": "blast:999", "chance": 50}
|
||||
check((not world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2).has("blast")), "Alternative output cannot restore disabled blast")
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
var rd = world.catalog.entities.RD
|
||||
check(rd.planets.HP.chance == 0.01, "Preserve fractional planet probability")
|
||||
check(rd.planets.GG.amount == 2.0 and rd.planets.GG.chance == 0.08, "Read count and chance separately")
|
||||
check(rd._parse_planet_string("HP:1:0.01") == {"HP": {"amount": 1.0, "chance": 0.01}}, "Accept a single planet term")
|
||||
check(E.eq(rd.planets.HP.chance, E.of("0.01")), "Preserve fractional planet probability")
|
||||
check((E.eq(rd.planets.GG.amount, 2) and E.eq(rd.planets.GG.chance, E.of("0.08"))), "Read count and chance separately")
|
||||
check(E.eq(rd._parse_planet_string("HP:1:0.01"), {"HP": {"amount": 1, "chance": E.of("0.01")}}), "Accept a single planet term")
|
||||
check(rd._parse_planet_string("").is_empty(), "Empty planet definitions stay empty")
|
||||
for id in ["HP", "EON1", "C0.9", "C1.2"]:
|
||||
check(world.catalog.entities[id].blastability, "Blast vulnerable: " + id)
|
||||
check(world.catalog.entities[id].blastability, E.add("Blast vulnerable: ", id))
|
||||
for id in ["RP", "GG", "AB", "C1.3", "C1.4", "C1.5"]:
|
||||
check(not world.catalog.entities[id].blastability, "Blast protected: " + id)
|
||||
check((not world.catalog.entities[id].blastability), E.add("Blast protected: ", id))
|
||||
|
||||
# Actual GI table: HRG -> SN, with 1% GRB; no random rounding.
|
||||
var totals: Dictionary = {}
|
||||
for i in 100:
|
||||
var products: Dictionary = world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 1.0)
|
||||
var products: Dictionary = world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 1)
|
||||
for id in products:
|
||||
totals[id] = totals.get(id, 0.0) + products[id]
|
||||
check(totals.get("SN") == 99.0 and totals.get("GRB") == 1.0, "GI rare outcomes accumulate deterministically")
|
||||
totals[id] = E.add(totals.get(id, 0), products[id])
|
||||
check((E.eq(totals.get("SN"), 99) and E.eq(totals.get("GRB"), 1)), "GI rare outcomes accumulate deterministically")
|
||||
var restored = fixture()
|
||||
restored.state = bytes_to_var(var_to_bytes(world.state))
|
||||
check(world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 37.0) == restored.stellar.evolution_products(restored.state, restored.catalog.entities.HRG, 37.0), "Evolution carry survives serialization")
|
||||
restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
check(E.eq(world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 37), restored.stellar.evolution_products(restored.state, restored.catalog.entities.HRG, 37)), "Evolution carry survives serialization")
|
||||
dispose(restored)
|
||||
|
||||
# Real RD lifespan, four stars spread over the existing lifespan/40 window.
|
||||
world.state.amount.N = 0.0
|
||||
world.state.amount.RD = 4.0
|
||||
world.batches.append_batch(world.state, "RD", 4.0, 0.0, 0.0)
|
||||
var lifespan: float = rd.lifespan
|
||||
var width: float = rd.lifespan_delta
|
||||
world.state.amount.N = 0
|
||||
world.state.amount.RD = 4
|
||||
world.batches.append_batch(world.state, "RD", 4, 0, 0)
|
||||
var lifespan: Variant = rd.lifespan
|
||||
var width: Variant = rd.lifespan_delta
|
||||
world.evolution.evolve_batches(world.state, lifespan)
|
||||
check(world.state.amount.RD == 4.0, "Window start does not kill the whole batch")
|
||||
world.evolution.evolve_batches(world.state, lifespan + width / 4.0)
|
||||
check(world.state.amount.RD == 3.0 and world.state.amount.TWD == 1.0, "One quarter of a batch evolves")
|
||||
world.evolution.evolve_batches(world.state, lifespan + width / 2.0)
|
||||
check(world.state.amount.RD == 2.0 and world.state.amount.TWD == 2.0, "Half the batch evolves")
|
||||
world.evolution.evolve_batches(world.state, lifespan + width)
|
||||
check(world.state.amount.RD == 0.0 and world.state.amount.TWD == 4.0, "Window end completes evolution")
|
||||
check(E.eq(world.state.amount.RD, 4), "Window start does not kill the whole batch")
|
||||
world.evolution.evolve_batches(world.state, E.add(lifespan, E.divide(width, 4)))
|
||||
check((E.eq(world.state.amount.RD, 3) and E.eq(world.state.amount.TWD, 1)), "One quarter of a batch evolves")
|
||||
world.evolution.evolve_batches(world.state, E.add(lifespan, E.divide(width, 2)))
|
||||
check((E.eq(world.state.amount.RD, 2) and E.eq(world.state.amount.TWD, 2)), "Half the batch evolves")
|
||||
world.evolution.evolve_batches(world.state, E.add(lifespan, width))
|
||||
check((E.eq(world.state.amount.RD, 0) and E.eq(world.state.amount.TWD, 4)), "Window end completes evolution")
|
||||
check(world.state.batches.RD.is_empty(), "Completed batch removed")
|
||||
dispose(world)
|
||||
# Creation accumulates independently for each star/planet pair.
|
||||
world = fixture()
|
||||
world.state.amount.RD = 100.0
|
||||
world.state.amount.RD = 100
|
||||
for i in 100:
|
||||
world.stellar.create_planets(world.state, "RD", 1.0, 0.0, 0.0, 1.0)
|
||||
check(world.state.binds.RD.HP == 1.0, "100 RD at metallicity 1 produce one HP")
|
||||
check(world.state.binds.RD.RP == 21.0, "Rocky planet probability retains fractions")
|
||||
check(world.state.binds.RD.GG == 16.0 and world.state.binds.RD.AB == 120.0, "Planet multiplicity applied")
|
||||
check(world.state.amount.HP == 0.0 and world.state.stats.total_amount.HP == 1.0, "Created planets are bound, counted once")
|
||||
world.stellar.create_planets(world.state, "RD", 1, 0, 0, 1)
|
||||
check(E.eq(world.state.binds.RD.HP, 1), "100 RD at metallicity 1 produce one HP")
|
||||
check(E.eq(world.state.binds.RD.RP, 21), "Rocky planet probability retains fractions")
|
||||
check((E.eq(world.state.binds.RD.GG, 16) and E.eq(world.state.binds.RD.AB, 120)), "Planet multiplicity applied")
|
||||
check((E.eq(world.state.amount.HP, 0) and E.eq(world.state.stats.total_amount.HP, 1)), "Created planets are bound, counted once")
|
||||
var saves: Node = root.get_node("SaveManager")
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Planet carry save validates")
|
||||
check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Planet carry save validates")
|
||||
restored = fixture()
|
||||
restored.state = bytes_to_var(var_to_bytes(world.state))
|
||||
world.stellar.create_planets(world.state, "RD", 37.0, 0.0, 0.0, 0.7)
|
||||
restored.stellar.create_planets(restored.state, "RD", 37.0, 0.0, 0.0, 0.7)
|
||||
check(world.state == restored.state, "Planet carry continues after save")
|
||||
restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
world.stellar.create_planets(world.state, "RD", 37, 0, 0, E.of("0.7"))
|
||||
restored.stellar.create_planets(restored.state, "RD", 37, 0, 0, E.of("0.7"))
|
||||
check(E.eq(world.state, restored.state), "Planet carry continues after save")
|
||||
dispose(restored)
|
||||
dispose(world)
|
||||
|
||||
# A partially dying dwarf sterilizes HP; survivors and RP move to its heir.
|
||||
world = fixture()
|
||||
world.state.amount.RD = 4.0
|
||||
world.state.binds.RD = {"HP": 8.0, "RP": 4.0, "C1.2": 4.0, "C1.3": 4.0, "GG": 4.0}
|
||||
world.batches.append_batch(world.state, "RD", 4.0, 0.0, 0.0)
|
||||
world.evolution.evolve_batches(world.state, lifespan + width / 2.0)
|
||||
check(world.state.amount.RD == 2.0 and world.state.amount.TWD == 2.0, "Partial star transition with planets")
|
||||
check(world.state.binds.RD.HP + world.state.binds.TWD.HP == 4.0, "Half of HP sterilized")
|
||||
check(world.state.binds.RD["C1.3"] == 2.0 and world.state.binds.TWD["C1.3"] == 2.0, "Protected civilization splits between parent and heir")
|
||||
check(world.state.binds.RD.RP + world.state.binds.TWD.RP == 10.0, "Sterilized planets become RP without loss")
|
||||
check(world.state.amount.RP == 0.0 and world.state.amount["C1.3"] == 0.0, "Inherited planets never become free")
|
||||
world.evolution.evolve_batches(world.state, lifespan + width)
|
||||
check(world.state.binds.RD["C1.3"] == 0.0 and world.state.binds.TWD["C1.3"] == 4.0, "No planets left at extinct parent type")
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Inherited state validates")
|
||||
world.state.amount.RD = 4
|
||||
world.state.binds.RD = {"HP": 8, "RP": 4, "C1.2": 4, "C1.3": 4, "GG": 4}
|
||||
world.batches.append_batch(world.state, "RD", 4, 0, 0)
|
||||
world.evolution.evolve_batches(world.state, E.add(lifespan, E.divide(width, 2)))
|
||||
check((E.eq(world.state.amount.RD, 2) and E.eq(world.state.amount.TWD, 2)), "Partial star transition with planets")
|
||||
check(E.eq(E.add(world.state.binds.RD.HP, world.state.binds.TWD.HP), 4), "Half of HP sterilized")
|
||||
check((E.eq(world.state.binds.RD["C1.3"], 2) and E.eq(world.state.binds.TWD["C1.3"], 2)), "Protected civilization splits between parent and heir")
|
||||
check(E.eq(E.add(world.state.binds.RD.RP, world.state.binds.TWD.RP), 10), "Sterilized planets become RP without loss")
|
||||
check((E.eq(world.state.amount.RP, 0) and E.eq(world.state.amount["C1.3"], 0)), "Inherited planets never become free")
|
||||
world.evolution.evolve_batches(world.state, E.add(lifespan, width))
|
||||
check((E.eq(world.state.binds.RD["C1.3"], 0) and E.eq(world.state.binds.TWD["C1.3"], 4)), "No planets left at extinct parent type")
|
||||
check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Inherited state validates")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.WS = 2.0
|
||||
world.state.binds.WS = {"HP": 4.0, "C1.3": 4.0, "RP": 4.0, "GG": 4.0}
|
||||
world.batches.append_batch(world.state, "WS", 2.0, 0.0, 0.0)
|
||||
world.evolution.evolve_batches(world.state, world.catalog.entities.WS.lifespan + world.catalog.entities.WS.lifespan_delta)
|
||||
world.state.amount.WS = 2
|
||||
world.state.binds.WS = {"HP": 4, "C1.3": 4, "RP": 4, "GG": 4}
|
||||
world.batches.append_batch(world.state, "WS", 2, 0, 0)
|
||||
world.evolution.evolve_batches(world.state, E.add(world.catalog.entities.WS.lifespan, world.catalog.entities.WS.lifespan_delta))
|
||||
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
check(physical.HP == 0.0 and physical["C1.3"] == 0.0 and physical.RP == 0.0 and physical.GG == 0.0, "Level 2 removes all planets, including protected civilization")
|
||||
check(world.state.amount.LRG == 2.0, "Giant evolves after local destruction")
|
||||
check((((E.eq(physical.HP, 0) and E.eq(physical["C1.3"], 0)) and E.eq(physical.RP, 0)) and E.eq(physical.GG, 0)), "Level 2 removes all planets, including protected civilization")
|
||||
check(E.eq(world.state.amount.LRG, 2), "Giant evolves after local destruction")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 10.0
|
||||
world.state.binds.RD = {"HP": 10.0, "C1.2": 10.0, "C1.3": 10.0, "RP": 0.0}
|
||||
world.stellar.apply_blast(world.state, 5.0, {"blast": 1.0, "density": 1.0})
|
||||
check(world.state.binds.RD.HP == 5.0 and world.state.binds.RD["C1.2"] == 5.0, "Blast damages vulnerable worlds proportionally")
|
||||
check(world.state.binds.RD["C1.3"] == 10.0 and world.state.binds.RD.RP == 10.0, "Blast protects C1.3 and creates RP")
|
||||
world.stellar.apply_blast(world.state, 1e6, {"blast": 1.0, "density": 1.0})
|
||||
check(world.state.binds.RD.HP == 0.0 and world.state.binds.RD.RP == 20.0, "Overwhelming blast is capped at available planets")
|
||||
check(not world.state.amount.has("blast"), "Blast is not a resource")
|
||||
world.state.amount.RD = 10
|
||||
world.state.binds.RD = {"HP": 10, "C1.2": 10, "C1.3": 10, "RP": 0}
|
||||
world.stellar.apply_blast(world.state, 5, {"blast": 1, "density": 1})
|
||||
check((E.eq(world.state.binds.RD.HP, 5) and E.eq(world.state.binds.RD["C1.2"], 5)), "Blast damages vulnerable worlds proportionally")
|
||||
check((E.eq(world.state.binds.RD["C1.3"], 10) and E.eq(world.state.binds.RD.RP, 10)), "Blast protects C1.3 and creates RP")
|
||||
world.stellar.apply_blast(world.state, 1000000, {"blast": 1, "density": 1})
|
||||
check((E.eq(world.state.binds.RD.HP, 0) and E.eq(world.state.binds.RD.RP, 20)), "Overwhelming blast is capped at available planets")
|
||||
check((not world.state.amount.has("blast")), "Blast is not a resource")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1.0
|
||||
world.state.binds.RD = {"HP": 1.0}
|
||||
world.state.amount.RD = 1
|
||||
world.state.binds.RD = {"HP": 1}
|
||||
for i in 10:
|
||||
world.stellar.apply_blast(world.state, 1.0, {"blast": 1.0, "density": 0.1})
|
||||
check(world.state.binds.RD.HP == 0.0 and world.state.binds.RD.RP == 1.0, "Small blast effects accumulate across cycles")
|
||||
world.stellar.apply_blast(world.state, 1, {"blast": 1, "density": E.of("0.1")})
|
||||
check((E.eq(world.state.binds.RD.HP, 0) and E.eq(world.state.binds.RD.RP, 1)), "Small blast effects accumulate across cycles")
|
||||
dispose(world)
|
||||
|
||||
# Actual supernova output: one SN gives a neutron star, resources, and an event.
|
||||
world = fixture()
|
||||
world.state.amount.SN = 1.0
|
||||
world.batches.append_batch(world.state, "SN", 1.0, 0.0, 0.0)
|
||||
world.evolution.evolve_batches(world.state, 2.0)
|
||||
check(world.state.amount.SN == 0.0 and world.state.amount.LNS == 1.0, "Supernova produces a remnant")
|
||||
check(world.state.amount.H == 8.0 and world.state.amount.He == 50.0 and world.state.amount.Met == 11.0, "Supernova returns table resources")
|
||||
check(not world.state.amount.has("blast"), "Supernova dispatches blast output")
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Blast state validates")
|
||||
world.state.amount.SN = 1
|
||||
world.batches.append_batch(world.state, "SN", 1, 0, 0)
|
||||
world.evolution.evolve_batches(world.state, 2)
|
||||
check((E.eq(world.state.amount.SN, 0) and E.eq(world.state.amount.LNS, 1)), "Supernova produces a remnant")
|
||||
check(((E.eq(world.state.amount.H, 8) and E.eq(world.state.amount.He, 50)) and E.eq(world.state.amount.Met, 11)), "Supernova returns table resources")
|
||||
check((not world.state.amount.has("blast")), "Supernova dispatches blast output")
|
||||
check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Blast state validates")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.H = 1000.0
|
||||
world.state.amount.Met = 100.0
|
||||
check(world.production.produce("RD", 100.0) == 100.0, "Manual star creation succeeds")
|
||||
check(world.state.binds.RD.HP >= 1.0, "Manual creation dispatches planet formation")
|
||||
check(world.state.amount.H == 900.0, "Planet creation does not duplicate or spend stellar fuel")
|
||||
world.state.amount.H = 1000
|
||||
world.state.amount.Met = 100
|
||||
check(E.eq(world.production.produce("RD", 100), 100), "Manual star creation succeeds")
|
||||
check(E.ge(world.state.binds.RD.HP, 1), "Manual creation dispatches planet formation")
|
||||
check(E.eq(world.state.amount.H, 900), "Planet creation does not duplicate or spend stellar fuel")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.N = 0.0
|
||||
world.state.amount.RD = 1.0
|
||||
world.state.amount.H = 100.0
|
||||
world.state.amount.Met = 100.0
|
||||
world.state.auto_resources.H = 100.0
|
||||
world.evolute(0.1)
|
||||
check(world.state.amount.RD == 101.0 and world.state.amount.H == 0.0, "Automatic formation uses its resource budget")
|
||||
check(world.state.binds.RD.HP >= 1.0, "Automatic formation also creates planets")
|
||||
world.state.amount.N = 0
|
||||
world.state.amount.RD = 1
|
||||
world.state.amount.H = 100
|
||||
world.state.amount.Met = 100
|
||||
world.state.auto_resources.H = 100
|
||||
world.evolute(E.of("0.1"))
|
||||
check((E.eq(world.state.amount.RD, 101) and E.eq(world.state.amount.H, 0)), "Automatic formation uses its resource budget")
|
||||
check(E.ge(world.state.binds.RD.HP, 1), "Automatic formation also creates planets")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.catalog.entities.N.auto = 1.0
|
||||
world.catalog.entities.N.auto = 1
|
||||
# 1000 newly produced automatic H reserve 1 H, enough for 10 HBD.
|
||||
world.evolute(100.0)
|
||||
check(world.state.amount.HBD == 10.0, "Brown dwarfs form without existing ordinary stars")
|
||||
check(world.state.amount.H == 999.0 and world.state.auto_resources.H == 999.0, "Brown dwarfs consume exactly their reserved hydrogen")
|
||||
check(world.presentation.amounts(world.state, world.catalog).BD == 10.0 and world.state.batches.HBD.size() > 0, "Brown dwarfs appear in counter and evolution batches")
|
||||
world.catalog.entities.N.product_per_year_amount = 0.0
|
||||
world.evolute(100.0)
|
||||
check(world.state.amount.HBD == 10.0, "Stored automatic budget is not taxed repeatedly")
|
||||
world.evolute(100)
|
||||
check(E.eq(world.state.amount.HBD, 10), "Brown dwarfs form without existing ordinary stars")
|
||||
check((E.eq(world.state.amount.H, 999) and E.eq(world.state.auto_resources.H, 999)), "Brown dwarfs consume exactly their reserved hydrogen")
|
||||
check((E.eq(world.presentation.amounts(world.state, world.catalog).BD, 10) and E.gt(world.state.batches.HBD.size(), 0)), "Brown dwarfs appear in counter and evolution batches")
|
||||
world.catalog.entities.N.product_per_year_amount = 0
|
||||
world.evolute(100)
|
||||
check(E.eq(world.state.amount.HBD, 10), "Stored automatic budget is not taxed repeatedly")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.catalog.entities.N.auto = 1.0
|
||||
world.state.amount.RD = 1.0
|
||||
world.evolute(50.0)
|
||||
check(world.state.amount.HBD == 5.0 and is_equal_approx(world.state.auto_star_deficits.HBD_fuel, 0.5), "Individual dwarfs retain prepaid fractional fuel")
|
||||
check(world.state.amount.H == 0.0, "Prepaid fuel is not available to ordinary stars")
|
||||
world.catalog.entities.N.auto = 1
|
||||
world.state.amount.RD = 1
|
||||
world.evolute(50)
|
||||
check((E.eq(world.state.amount.HBD, 5) and E.near(world.state.auto_star_deficits.HBD_fuel, E.of("0.5"))), "Individual dwarfs retain prepaid fractional fuel")
|
||||
check(E.eq(world.state.amount.H, 0), "Prepaid fuel is not available to ordinary stars")
|
||||
var resumed = fixture()
|
||||
resumed.catalog.entities.N.auto = 1.0
|
||||
resumed.state = bytes_to_var(var_to_bytes(world.state))
|
||||
world.evolute(50.0)
|
||||
resumed.evolute(50.0)
|
||||
check(world.state.amount.HBD == 10.0, "Accumulated quota eventually creates a whole batch")
|
||||
check(world.state == resumed.state, "Brown dwarf quota survives serialization deterministically")
|
||||
check(world.state.amount.RD == 1000.0 and world.state.amount.H == 0.0, "Shared automatic hydrogen is spent once")
|
||||
world.state.batches.HBD = [{"amount": 10.0, "start_year": 101.0, "end_year": 101.0}]
|
||||
world.evolution.evolve_batches(world.state, 101.0)
|
||||
check(world.state.amount.HBD == 0.0 and world.state.amount.CBD == 10.0 and world.presentation.amounts(world.state, world.catalog).BD == 10.0, "Cooling preserves aggregate brown dwarf count")
|
||||
resumed.catalog.entities.N.auto = 1
|
||||
resumed.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
world.evolute(50)
|
||||
resumed.evolute(50)
|
||||
check(E.eq(world.state.amount.HBD, 10), "Accumulated quota eventually creates a whole batch")
|
||||
check(E.eq(world.state, resumed.state), "Brown dwarf quota survives serialization deterministically")
|
||||
check((E.eq(world.state.amount.RD, 1000) and E.eq(world.state.amount.H, 0)), "Shared automatic hydrogen is spent once")
|
||||
world.state.batches.HBD = [{"amount": 10, "start_year": 101, "end_year": 101}]
|
||||
world.evolution.evolve_batches(world.state, 101)
|
||||
check(((E.eq(world.state.amount.HBD, 0) and E.eq(world.state.amount.CBD, 10)) and E.eq(world.presentation.amounts(world.state, world.catalog).BD, 10)), "Cooling preserves aggregate brown dwarf count")
|
||||
dispose(resumed)
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
var saw_individual: bool = false
|
||||
for i in range(250):
|
||||
world.state.amount.H += 1.0
|
||||
world.state.auto_resources.H = world.state.auto_resources.get("H", 0.0) + 1.0
|
||||
var before: float = world.state.amount.HBD
|
||||
world.automatic.produce_brown_dwarfs(world.state, 1.0, float(i), float(i + 1))
|
||||
var born: float = world.state.amount.HBD - before
|
||||
check(born == 0.0 or born == 1.0, "Small fuel increments produce individual dwarfs")
|
||||
saw_individual = saw_individual or born == 1.0
|
||||
world.state.amount.H = E.add(world.state.amount.H, 1)
|
||||
world.state.auto_resources.H = E.add(world.state.auto_resources.get("H", 0), 1)
|
||||
var before: Variant = world.state.amount.HBD
|
||||
world.automatic.produce_brown_dwarfs(world.state, 1, E.of(i), E.of(E.add(i, 1)))
|
||||
var born: Variant = E.subtract(world.state.amount.HBD, before)
|
||||
check((E.eq(born, 0) or E.eq(born, 1)), "Small fuel increments produce individual dwarfs")
|
||||
saw_individual = (saw_individual or E.eq(born, 1))
|
||||
check(saw_individual, "Random rounding does not wait for ten dwarfs")
|
||||
resumed = fixture()
|
||||
resumed.state = bytes_to_var(var_to_bytes(world.state))
|
||||
resumed.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
for i in range(250, 500):
|
||||
for simulation in [world, resumed]:
|
||||
simulation.state.amount.H += 1.0
|
||||
simulation.state.auto_resources.H += 1.0
|
||||
simulation.automatic.produce_brown_dwarfs(simulation.state, 1.0, float(i), float(i + 1))
|
||||
check(world.state == resumed.state, "Random sequence and prepaid fuel survive saving")
|
||||
check(is_equal_approx(world.state.amount.H + world.state.amount.HBD * 0.1 + world.state.auto_star_deficits.HBD_fuel, 500.0), "Individual production conserves fuel including prepaid reserve")
|
||||
check(is_equal_approx(world.state.amount.HBD + world.state.auto_star_deficits.HBD, 5.0), "Signed remainder preserves the 0.1 percent allocation")
|
||||
var paused_count: float = world.state.amount.HBD
|
||||
for i in 10: world.automatic.produce_brown_dwarfs(world.state, 0.0, 500.0, 501.0)
|
||||
check(world.state.amount.HBD == paused_count, "No repeated random attempts without new fuel")
|
||||
simulation.state.amount.H = E.add(simulation.state.amount.H, 1)
|
||||
simulation.state.auto_resources.H = E.add(simulation.state.auto_resources.H, 1)
|
||||
simulation.automatic.produce_brown_dwarfs(simulation.state, 1, E.of(i), E.of(E.add(i, 1)))
|
||||
check(E.eq(world.state, resumed.state), "Random sequence and prepaid fuel survive saving")
|
||||
check(E.near(E.add(E.add(world.state.amount.H, E.multiply(world.state.amount.HBD, E.of("0.1"))), world.state.auto_star_deficits.HBD_fuel), 500), "Individual production conserves fuel including prepaid reserve")
|
||||
check(E.near(E.add(world.state.amount.HBD, world.state.auto_star_deficits.HBD), 5), "Signed remainder preserves the 0.1 percent allocation")
|
||||
var paused_count: Variant = world.state.amount.HBD
|
||||
for i in 10: world.automatic.produce_brown_dwarfs(world.state, 0, 500, 501)
|
||||
check(E.eq(world.state.amount.HBD, paused_count), "No repeated random attempts without new fuel")
|
||||
dispose(resumed)
|
||||
dispose(world)
|
||||
|
||||
# Two simultaneous successors share surviving planets in the same 99:1 ratio.
|
||||
world = fixture()
|
||||
world.catalog.entities.HRG.destroy_level = 0
|
||||
world.state.amount.HRG = 100.0
|
||||
world.state.binds.HRG = {"C1.3": 100.0}
|
||||
world.state.batches.HRG = [{"start_year": 0.1, "end_year": 0.1, "amount": 100.0}]
|
||||
world.evolution.evolve_batches(world.state, 0.1)
|
||||
check(world.state.binds.SN["C1.3"] == 99.0 and world.state.binds.GRB["C1.3"] == 1.0, "Planets split among actual alternative successors")
|
||||
check(world.state.binds.HRG["C1.3"] == 0.0, "Alternative evolution leaves no orphan bindings")
|
||||
world.state.amount.HRG = 100
|
||||
world.state.binds.HRG = {"C1.3": 100}
|
||||
world.state.batches.HRG = [{"start_year": E.of("0.1"), "end_year": E.of("0.1"), "amount": 100}]
|
||||
world.evolution.evolve_batches(world.state, E.of("0.1"))
|
||||
check((E.eq(world.state.binds.SN["C1.3"], 99) and E.eq(world.state.binds.GRB["C1.3"], 1)), "Planets split among actual alternative successors")
|
||||
check(E.eq(world.state.binds.HRG["C1.3"], 0), "Alternative evolution leaves no orphan bindings")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.N = 0.0
|
||||
world.state.amount.HRG = 1.0
|
||||
world.state.batches.HRG = [{"start_year": 0.1, "end_year": 0.1, "amount": 1.0}]
|
||||
world.evolute(3.0)
|
||||
check(world.state.amount.LNS == 1.0 and world.state.amount.SN == 0.0, "Long step follows multiple stellar generations")
|
||||
check(world.state.amount.S >= 99.0, "Short-lived newborn supernova emits S before disappearing")
|
||||
world.state.amount.N = 0
|
||||
world.state.amount.HRG = 1
|
||||
world.state.batches.HRG = [{"start_year": E.of("0.1"), "end_year": E.of("0.1"), "amount": 1}]
|
||||
world.evolute(3)
|
||||
check((E.eq(world.state.amount.LNS, 1) and E.eq(world.state.amount.SN, 0)), "Long step follows multiple stellar generations")
|
||||
check(E.ge(world.state.amount.S, 99), "Short-lived newborn supernova emits S before disappearing")
|
||||
dispose(world)
|
||||
|
||||
print("Stellar rules: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Stellar rules: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+42
-41
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -38,55 +39,55 @@ func dispose(world) -> void:
|
||||
func run() -> void:
|
||||
var saves: Node = root.get_node("SaveManager")
|
||||
var world = fixture()
|
||||
world.evolute(0.09)
|
||||
check(world.state.time.year == 0.0, "Do not process before a tenth boundary")
|
||||
check(world.state.amount.H == 0.0, "No premature production")
|
||||
world.evolute(0.01)
|
||||
check(world.state.time.year == 0.1, "Process the first tenth")
|
||||
check(world.state.amount.H == 1.0, "Production runs at 0.1 year")
|
||||
world.evolute(0.2)
|
||||
check(world.state.time.year == 0.3 and world.state.amount.H == 3.0, "Do not reprocess previous tenths")
|
||||
world.evolute(E.of("0.09"))
|
||||
check(E.eq(world.state.time.year, 0), "Do not process before a tenth boundary")
|
||||
check(E.eq(world.state.amount.H, 0), "No premature production")
|
||||
world.evolute(E.of("0.01"))
|
||||
check(E.eq(world.state.time.year, E.of("0.1")), "Process the first tenth")
|
||||
check(E.eq(world.state.amount.H, 1), "Production runs at 0.1 year")
|
||||
world.evolute(E.of("0.2"))
|
||||
check((E.eq(world.state.time.year, E.of("0.3")) and E.eq(world.state.amount.H, 3)), "Do not reprocess previous tenths")
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
for invalid in [0.0, -1.0, NAN, INF, 1e308]:
|
||||
for invalid in [0, E.neg(1), NAN, INF, E.of("1e308")]:
|
||||
world.evolute(invalid)
|
||||
check(world.state == before, "Invalid/overflow interval rejected")
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Fractional save is valid")
|
||||
check(E.eq(world.state, before), "Invalid/overflow interval rejected")
|
||||
check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Fractional save is valid")
|
||||
var invalid_save: Dictionary = world.state.duplicate(true)
|
||||
invalid_save.time.year = 0.25
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": invalid_save}) != "", "Reject inconsistent fractional clock")
|
||||
invalid_save.time.year = E.of("0.25")
|
||||
check(E.ne(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": invalid_save}), ""), "Reject inconsistent fractional clock")
|
||||
dispose(world)
|
||||
|
||||
for fps in [30, 60, 144]:
|
||||
world = fixture()
|
||||
for frame in fps:
|
||||
world.evolute(1.0 / fps)
|
||||
check(world.state.time.year == 1.0, "One second at %d fps" % fps)
|
||||
check(world.state.amount.H == 10.0, "Production across frames at %d fps" % fps)
|
||||
world.evolute(E.divide(1, fps))
|
||||
check(E.eq(world.state.time.year, 1), ("One second at %d fps" % fps))
|
||||
check(E.eq(world.state.amount.H, 10), ("Production across frames at %d fps" % fps))
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.evolute(0.05)
|
||||
world.evolute(E.of("0.05"))
|
||||
world.speed = 2
|
||||
world.evolute(0.005)
|
||||
check(world.state.time.year == 0.1, "Speed change preserves accumulated fraction")
|
||||
world.evolute(E.of("0.005"))
|
||||
check(E.eq(world.state.time.year, E.of("0.1")), "Speed change preserves accumulated fraction")
|
||||
world.speed = 100
|
||||
var speed_limit: int = root.get_node("Global").SPEED_LIMIT
|
||||
check(world.speed == speed_limit and world.state.speed_in_year == pow(10.0, speed_limit - 1), "Configured upper speed limit")
|
||||
world.speed = -10
|
||||
check(world.speed == 1 and world.state.speed_in_year == 1.0, "Lower speed limit unchanged")
|
||||
check((E.eq(world.speed, speed_limit) and E.eq(world.state.speed_in_year, E.power(10, E.subtract(speed_limit, 1)))), "Configured upper speed limit")
|
||||
world.speed = E.neg(10)
|
||||
check((E.eq(world.speed, 1) and E.eq(world.state.speed_in_year, 1)), "Lower speed limit unchanged")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.catalog.entities.N.output_and = {"He": 1.0}
|
||||
world.catalog.entities.N.output_and = {"He": 1}
|
||||
world.catalog.entities.N.output_or = {}
|
||||
world.state.batches.N = [{"start_year": 0.1, "end_year": 0.1, "amount": 1.0}]
|
||||
world.evolute(0.1)
|
||||
check(world.state.amount.N == 0.0 and world.state.amount.He == 1.0, "Existing evolution executes at fractional boundary")
|
||||
world.state.batches.N = [{"start_year": E.of("0.1"), "end_year": E.of("0.1"), "amount": 1}]
|
||||
world.evolute(E.of("0.1"))
|
||||
check((E.eq(world.state.amount.N, 0) and E.eq(world.state.amount.He, 1)), "Existing evolution executes at fractional boundary")
|
||||
dispose(world)
|
||||
check(Clock.processed_year(0.099) == 0.0, "Do not round a genuine incomplete tenth forward")
|
||||
check(Clock.processed_year(1e9 + 0.45) == 1e9 + 0.4, "Large ages preserve fractional precision")
|
||||
print("Time state: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
check(E.eq(Clock.processed_year(E.of("0.099")), 0), "Do not round a genuine incomplete tenth forward")
|
||||
check(E.eq(Clock.processed_year(E.add(E.of("1e9"), E.of("0.45"))), E.add(E.of("1e9"), E.of("0.4"))), "Large ages preserve fractional precision")
|
||||
print(("Time state: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+20
-19
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not value):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
@@ -22,39 +23,39 @@ func run() -> void:
|
||||
await process_frame
|
||||
stage.stage_page_scroller.scroll_horizontal = stage.PAGE_WIDTH
|
||||
await process_frame
|
||||
stage.owniverse.state.amount.H = 1000.0
|
||||
stage.owniverse.state.amount.RD = 2.0
|
||||
stage.owniverse.state.amount.H = 1000
|
||||
stage.owniverse.state.amount.RD = 2
|
||||
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog)
|
||||
stage.set_owniverse_item_value()
|
||||
var hydrogen = stage.owniverse_items.H
|
||||
var dwarf = stage.owniverse_items.RD
|
||||
check(hydrogen.description.text == "1 000", "Pending quantities reach controls")
|
||||
check(dwarf.description.text == "2", "Other page receives its current quantity")
|
||||
check(E.eq(hydrogen.description.text, "1 000"), "Pending quantities reach controls")
|
||||
check(E.eq(dwarf.description.text, "2"), "Other page receives its current quantity")
|
||||
check(stage.owniverse.presentation.pending_amounts.is_empty(), "Published queue is consumed by Stage")
|
||||
stage.owniverse.presentation.deferred_updates = true
|
||||
stage.owniverse.state.amount.H = 2000.0
|
||||
stage.owniverse.state.amount.H = 2000
|
||||
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog)
|
||||
check(stage.owniverse.presentation.dirty and stage.owniverse.presentation.pending_amounts.is_empty(), "Deferred refresh marks dirty without republishing")
|
||||
check((stage.owniverse.presentation.dirty and stage.owniverse.presentation.pending_amounts.is_empty()), "Deferred refresh marks dirty without republishing")
|
||||
stage.set_owniverse_item_value()
|
||||
check(hydrogen.description.text == "1 000", "Labels retain last published quantity")
|
||||
check(E.eq(hydrogen.description.text, "1 000"), "Labels retain last published quantity")
|
||||
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog, true)
|
||||
stage.set_owniverse_item_value()
|
||||
check(hydrogen.description.text == "2 000" and not stage.owniverse.presentation.dirty, "Forced refresh publishes latest state")
|
||||
check((E.eq(hydrogen.description.text, "2 000") and (not stage.owniverse.presentation.dirty)), "Forced refresh publishes latest state")
|
||||
stage.owniverse.presentation.deferred_updates = false
|
||||
stage.item_activated(hydrogen)
|
||||
stage.set_owniverse_item_value()
|
||||
check(hydrogen.description.text == root.get_node("Global").format_number(stage.owniverse.state.amount.H), "Player production publishes the new quantity")
|
||||
check(stage.owniverse.state.amount.H > 2000.0, "Production action actually executed")
|
||||
check(E.eq(hydrogen.description.text, root.get_node("Global").format_number(stage.owniverse.state.amount.H)), "Player production publishes the new quantity")
|
||||
check(E.gt(stage.owniverse.state.amount.H, 2000), "Production action actually executed")
|
||||
dwarf.visible = true
|
||||
stage.stage_page_scroller.scroll_horizontal = 2 * stage.PAGE_WIDTH
|
||||
stage.stage_page_scroller.scroll_horizontal = E.multiply(2, stage.PAGE_WIDTH)
|
||||
await process_frame
|
||||
stage._refresh_universe_ui()
|
||||
check(dwarf.description.text == "2", "Newly visible page receives cached latest values")
|
||||
stage.owniverse.state.time.year = 1234.5
|
||||
check(E.eq(dwarf.description.text, "2"), "Newly visible page receives cached latest values")
|
||||
stage.owniverse.state.time.year = E.of("1234.5")
|
||||
stage._refresh_universe_ui()
|
||||
check(stage.date_time.text == "1 234.5", "Time displays tenths with thousands separator")
|
||||
check(E.eq(stage.date_time.text, "1 234.5"), "Time displays tenths with thousands separator")
|
||||
for entity in stage.owniverse.catalog.entities.values(): entity.free()
|
||||
stage.owniverse.free()
|
||||
scene.free()
|
||||
print("UI updates: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("UI updates: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
+98
-101
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const CivilizationState = preload("res://world/owniverse/civilization_state.gd")
|
||||
const CivilizationEvolution = preload("res://world/owniverse/civilization_evolution.gd")
|
||||
|
||||
@@ -11,9 +12,9 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
@@ -21,11 +22,11 @@ func fixture():
|
||||
for id in root.get_node("Global").OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(root.get_node("Global").OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -35,116 +36,116 @@ func dispose(world) -> void:
|
||||
func run() -> void:
|
||||
var global: Node = root.get_node("Global")
|
||||
var world = fixture()
|
||||
world.state.amount.RD = 400.0
|
||||
world.batches.append_batch(world.state, "RD", 400.0, 0.0, 0.0)
|
||||
var end: float = world.state.batches.RD[0].end_year
|
||||
world.environment.modify_world_parameter("Bur", 9.0)
|
||||
check(is_equal_approx(world.state.batches.RD[0].end_year, end / 10.0), "Burnability accelerates existing cohorts")
|
||||
var count: float = world.state.batches.RD[0].amount
|
||||
world.environment.modify_world_parameter("Bur", -9.0)
|
||||
check(is_equal_approx(world.state.batches.RD[0].end_year, end) and world.state.batches.RD[0].amount == count, "Changing burnability back preserves population and lifetime")
|
||||
world.environment.modify_world_parameter("Bur", 9.0)
|
||||
world.batches.append_batch(world.state, "YD", 10.0, 0.0, 0.0)
|
||||
check(is_equal_approx(world.state.batches.YD[0].start_year, world.catalog.entities.YD.lifespan / 10.0), "New stars use the same burnability")
|
||||
world.state.time = {"datetime": end / 10.0 - 0.1, "year": end / 10.0 - 0.1}
|
||||
world.state.amount.RD = 400
|
||||
world.batches.append_batch(world.state, "RD", 400, 0, 0)
|
||||
var end: Variant = world.state.batches.RD[0].end_year
|
||||
world.environment.modify_world_parameter("Bur", 9)
|
||||
check(E.near(world.state.batches.RD[0].end_year, E.divide(end, 10)), "Burnability accelerates existing cohorts")
|
||||
var count: Variant = world.state.batches.RD[0].amount
|
||||
world.environment.modify_world_parameter("Bur", E.neg(9))
|
||||
check((E.near(world.state.batches.RD[0].end_year, end) and E.eq(world.state.batches.RD[0].amount, count)), "Changing burnability back preserves population and lifetime")
|
||||
world.environment.modify_world_parameter("Bur", 9)
|
||||
world.batches.append_batch(world.state, "YD", 10, 0, 0)
|
||||
check(E.near(world.state.batches.YD[0].start_year, E.divide(world.catalog.entities.YD.lifespan, 10)), "New stars use the same burnability")
|
||||
world.state.time = {"datetime": E.subtract(E.divide(end, 10), E.of("0.1")), "year": E.subtract(E.divide(end, 10), E.of("0.1"))}
|
||||
world.batches.withdraw_batches(world.state, "RD", world.state.time.year)
|
||||
world.environment.modify_world_parameter("Bur", -18.0)
|
||||
check(world.state.batches.RD[0].end_year >= world.state.time.year, "Slowing a partially evolved cohort keeps its remaining window in the future")
|
||||
world.environment.modify_world_parameter("Exp", 9.0)
|
||||
check(world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2.0).blast == 180.0, "Explosion power includes world explosiveness")
|
||||
var products: Dictionary = world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 1000.0)
|
||||
check(products.get("GRB", 0.0) == 100.0 and products.get("SN", 0.0) == 900.0, "Explosiveness raises rare-event probability without duplicating stars")
|
||||
world.catalog.entities.HRG.output_or.chance = 50.0
|
||||
products = world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 100.0)
|
||||
check(products.get("GRB", 0.0) == 100.0 and products.get("SN", 0.0) == 0.0, "Rare-event probability is bounded at one")
|
||||
world.environment.modify_world_parameter("Bur", E.neg(18))
|
||||
check(E.ge(world.state.batches.RD[0].end_year, world.state.time.year), "Slowing a partially evolved cohort keeps its remaining window in the future")
|
||||
world.environment.modify_world_parameter("Exp", 9)
|
||||
check(E.eq(world.stellar.evolution_products(world.state, world.catalog.entities.SN, 2).blast, 180), "Explosion power includes world explosiveness")
|
||||
var products: Dictionary = world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 1000)
|
||||
check((E.eq(products.get("GRB", 0), 100) and E.eq(products.get("SN", 0), 900)), "Explosiveness raises rare-event probability without duplicating stars")
|
||||
world.catalog.entities.HRG.output_or.chance = 50
|
||||
products = world.stellar.evolution_products(world.state, world.catalog.entities.HRG, 100)
|
||||
check((E.eq(products.get("GRB", 0), 100) and E.eq(products.get("SN", 0), 0)), "Rare-event probability is bounded at one")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
var ordinary: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000.0)
|
||||
var ordinary: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000)
|
||||
world.state.life_remainders.clear()
|
||||
world.environment.modify_world_parameter("Agg", 9.0)
|
||||
var aggressive: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000.0)
|
||||
check(aggressive.EON1 > ordinary.EON1, "Aggression accelerates primitive evolution")
|
||||
world.environment.modify_world_parameter("Agg", 9)
|
||||
var aggressive: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000)
|
||||
check(E.gt(aggressive.EON1, ordinary.EON1), "Aggression accelerates primitive evolution")
|
||||
world.state.life_remainders.clear()
|
||||
world.state.world_parameters.Order = 10.0
|
||||
var orderly: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000.0)
|
||||
check(orderly.RP < aggressive.RP, "Order reduces degradation")
|
||||
world.state.world_parameters.Order = 10
|
||||
var orderly: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000)
|
||||
check(E.lt(orderly.RP, aggressive.RP), "Order reduces degradation")
|
||||
world.state.life_remainders.clear()
|
||||
world.state.world_parameters.Enlightenment = 10.0
|
||||
var enlightened: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000.0)
|
||||
check(enlightened.EON1 > orderly.EON1, "Enlightenment increases primitive progress")
|
||||
world.state.world_parameters.Enlightenment = 10
|
||||
var enlightened: Dictionary = world.life.life_products(world.state, world.catalog.entities.HP, 1000000)
|
||||
check(E.gt(enlightened.EON1, orderly.EON1), "Enlightenment increases primitive progress")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
var hotter = fixture()
|
||||
for simulation in [world, hotter]:
|
||||
simulation.state.amount.RD = 1000000.0
|
||||
simulation.batches.append_batch(simulation.state, "RD", 1000000.0, 0.0, 0.0)
|
||||
hotter.environment.modify_world_parameter("Bur", 9.0)
|
||||
world.evolute(1000.0)
|
||||
hotter.evolute(1000.0)
|
||||
check(hotter.state.amount.S > world.state.amount.S, "Burnability changes actual space production")
|
||||
check(hotter.state.energy + hotter.state.amount.S == Parameters.INITIAL_ENERGY, "Enhanced production still spends the GI energy budget")
|
||||
simulation.state.amount.RD = 1000000
|
||||
simulation.batches.append_batch(simulation.state, "RD", 1000000, 0, 0)
|
||||
hotter.environment.modify_world_parameter("Bur", 9)
|
||||
world.evolute(1000)
|
||||
hotter.evolute(1000)
|
||||
check(E.gt(hotter.state.amount.S, world.state.amount.S), "Burnability changes actual space production")
|
||||
check(E.eq(E.add(hotter.state.energy, hotter.state.amount.S), Parameters.INITIAL_ENERGY), "Enhanced production still spends the GI energy budget")
|
||||
dispose(world)
|
||||
dispose(hotter)
|
||||
|
||||
world = fixture()
|
||||
world.state.amount["C0.2"] = 1000000.0
|
||||
world.state.ethics["C0.2"] = {"m": 1000000.0}
|
||||
world.state.amount["C0.2"] = 1000000
|
||||
world.state.ethics["C0.2"] = {"m": 1000000}
|
||||
var baseline: Dictionary = world.state.duplicate(true)
|
||||
var advanced: Dictionary = baseline.duplicate(true)
|
||||
advanced.world_parameters.Agg = 19.0
|
||||
advanced.world_parameters.Agg = 19
|
||||
var next_stage: String = global.CivilizationRules["C0.2"].next
|
||||
var outcomes: Array = []
|
||||
for target in [baseline, advanced]:
|
||||
var expired: Dictionary = {"C0.2": [{"amount": 1000000.0, "from": 0.0, "to": 1.0}]}
|
||||
var expired: Dictionary = {"C0.2": [{"amount": 1000000, "from": 0, "to": 1}]}
|
||||
var budget: Dictionary = CivilizationState.stellar_budget(world, target, global.CivilizationRules)
|
||||
CivilizationEvolution.prepare(world, target, expired, 1.0, budget, global.CivilizationRules)
|
||||
outcomes.append(expired["C0.2"][0].products.get(next_stage, 0.0))
|
||||
check(outcomes[1] > outcomes[0], "Aggression changes actual ethical civilization transitions")
|
||||
CivilizationEvolution.prepare(world, target, expired, 1, budget, global.CivilizationRules)
|
||||
outcomes.append(expired["C0.2"][0].products.get(next_stage, 0))
|
||||
check(E.gt(outcomes[1], outcomes[0]), "Aggression changes actual ethical civilization transitions")
|
||||
dispose(world)
|
||||
|
||||
# Trends use ethical proportions and stage weights, not population magnitude.
|
||||
world = fixture()
|
||||
var rules: Dictionary = {"C2.0": {"home": "DwStar"}, "C2.1": {"home": "DwStar"}}
|
||||
world.state.ethics = {"C2.0": {"d": 100.0}, "C2.1": {"d": 100.0}}
|
||||
world.state.ethics = {"C2.0": {"d": 100}, "C2.1": {"d": 100}}
|
||||
var source: Dictionary = world.state.duplicate(true)
|
||||
var single: Dictionary = source.duplicate(true)
|
||||
var split: Dictionary = source.duplicate(true)
|
||||
Parameters.advance(source, single, rules, 1.0e6)
|
||||
for i in 10: Parameters.advance(source, split, rules, 1.0e5)
|
||||
check(is_equal_approx(single.trends.Integration, 0.018), "Trend integrates elapsed years and doubled stage weights")
|
||||
Parameters.advance(source, single, rules, 1000000)
|
||||
for i in 10: Parameters.advance(source, split, rules, 100000)
|
||||
check(E.near(single.trends.Integration, E.of("0.018")), "Trend integrates elapsed years and doubled stage weights")
|
||||
for key in Parameters.TRENDS:
|
||||
check(is_equal_approx(single.trends[key], split.trends[key]), "Trend is independent of frame partition: " + key)
|
||||
source.ethics["C2.0"].d = 1000000.0
|
||||
check(E.near(single.trends[key], split.trends[key]), E.add("Trend is independent of frame partition: ", key))
|
||||
source.ethics["C2.0"].d = 1000000
|
||||
var larger: Dictionary = world.state.duplicate(true)
|
||||
Parameters.advance(source, larger, rules, 1.0e6)
|
||||
check(is_equal_approx(larger.trends.Integration, single.trends.Integration), "Population proportions determine trend influence")
|
||||
world.state.trends.Integration = 0.99
|
||||
Parameters.advance(source, world.state, rules, 1.0e8)
|
||||
check(world.state.civilization_stats["trend:Integration"] == 2.0, "Long steps process every crossed threshold")
|
||||
check(is_equal_approx(world.state.trends.Integration, 0.79), "Threshold crossing preserves the fractional remainder")
|
||||
check(world.state.world_parameters.Federation > 1.0 or world.state.world_parameters.Enlightenment > 1.0 or world.state.world_parameters.Order > 1.0, "Integration events change the world")
|
||||
var restored: Dictionary = bytes_to_var(var_to_bytes(world.state))
|
||||
Parameters.advance(source, world.state, rules, 1.0e8)
|
||||
Parameters.advance(source, restored, rules, 1.0e8)
|
||||
check(world.state == restored, "Trend events resume identically after serialization")
|
||||
Parameters.advance(source, world.state, rules, 1.0e12)
|
||||
check(world.state.trends.Integration >= 0.0 and world.state.trends.Integration < 1.0, "Very large intervals finish with a valid trend fraction")
|
||||
check(world.state.world_parameters.Agg >= 1.0, "Repeated integration events cannot reduce aggression below the scale")
|
||||
var payload: Dictionary = {"format_version": 2, "app_version": "test", "ui": {"current_page": 1}, "state": world.state}
|
||||
Parameters.advance(source, larger, rules, 1000000)
|
||||
check(E.near(larger.trends.Integration, single.trends.Integration), "Population proportions determine trend influence")
|
||||
world.state.trends.Integration = E.of("0.99")
|
||||
Parameters.advance(source, world.state, rules, 100000000)
|
||||
check(E.eq(world.state.civilization_stats["trend:Integration"], 2), "Long steps process every crossed threshold")
|
||||
check(E.near(world.state.trends.Integration, E.of("0.79")), "Threshold crossing preserves the fractional remainder")
|
||||
check(((E.gt(world.state.world_parameters.Federation, 1) or E.gt(world.state.world_parameters.Enlightenment, 1)) or E.gt(world.state.world_parameters.Order, 1)), "Integration events change the world")
|
||||
var restored: Dictionary = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state))))
|
||||
Parameters.advance(source, world.state, rules, 100000000)
|
||||
Parameters.advance(source, restored, rules, 100000000)
|
||||
check(E.eq(world.state, restored), "Trend events resume identically after serialization")
|
||||
Parameters.advance(source, world.state, rules, E.of("1.0e12"))
|
||||
check((E.ge(world.state.trends.Integration, 0) and E.lt(world.state.trends.Integration, 1)), "Very large intervals finish with a valid trend fraction")
|
||||
check(E.ge(world.state.world_parameters.Agg, 1), "Repeated integration events cannot reduce aggression below the scale")
|
||||
var payload: Dictionary = {"format_version": 3, "app_version": "test", "ui": {"current_page": 1}, "state": world.state}
|
||||
var manager: Node = root.get_node("SaveManager")
|
||||
check(manager.validate_data(payload) == "", "New parameters and trends pass save validation")
|
||||
check(E.eq(manager.validate_data(payload), ""), "New parameters and trends pass save validation")
|
||||
var old: Dictionary = payload.duplicate(true)
|
||||
old.state.erase("world_parameters")
|
||||
old.state.erase("trends")
|
||||
check(manager.validate_data(old) == "", "Existing current-format saves remain readable")
|
||||
payload.state.world_parameters.Agg = 20.0
|
||||
check(manager.validate_data(payload) != "", "Out-of-range scales are rejected")
|
||||
check(E.eq(manager.validate_data(old), ""), "Existing current-format saves remain readable")
|
||||
payload.state.world_parameters.Agg = 20
|
||||
check(E.ne(manager.validate_data(payload), ""), "Out-of-range scales are rejected")
|
||||
dispose(world)
|
||||
|
||||
for sample in [[1e-7, 0.0], [1e-6, 0.5], [1e-5, 1.0], [0.9, 0.0], [0.95, 0.5], [1.0, 1.0]]:
|
||||
check(is_equal_approx(Parameters.density_display(sample[0]).position, sample[1]), "Density boundary " + str(sample[0]))
|
||||
for sample in [[E.of("1e-7"), 0], [E.of("1e-6"), E.of("0.5")], [E.of("1e-5"), 1], [E.of("0.9"), 0], [E.of("0.95"), E.of("0.5")], [1, 1]]:
|
||||
check(E.near(Parameters.density_display(sample[0]).position, sample[1]), E.add("Density boundary ", str(sample[0])))
|
||||
# The fixture overrides saving/loading; the player's save is never accessed.
|
||||
var scene = load("res://world/world.tscn").instantiate()
|
||||
var stage = scene.get_node("Stage")
|
||||
@@ -153,40 +154,36 @@ func run() -> void:
|
||||
root.add_child(scene)
|
||||
await process_frame
|
||||
await process_frame
|
||||
stage.owniverse.state.energy = Parameters.INITIAL_ENERGY / 4.0
|
||||
stage.owniverse.state.world_parameters.Bur = 19.0
|
||||
stage.owniverse.state.trends.Integration = 0.75
|
||||
stage.owniverse.state.energy = E.divide(Parameters.INITIAL_ENERGY, 4)
|
||||
stage.owniverse.state.world_parameters.Bur = 19
|
||||
stage.owniverse.state.trends.Integration = E.of("0.75")
|
||||
stage.world_indicators.update(stage.owniverse)
|
||||
var warning: TextureRect = scene.get_node("Background/WarningBorder")
|
||||
check(warning.mouse_filter == Control.MOUSE_FILTER_IGNORE, "Warning overlay does not block input")
|
||||
var environments: Array = [
|
||||
[1.0e-7, "ff0000ff"], [1.0e-6, "df2020bf"], [1.0e-5, "c040407f"],
|
||||
[0.1, "00000000"], [0.9, "40a0c07f"], [0.97, "20b0dfbf"], [1.0, "00c0ffff"],
|
||||
[0.1, "00000000"],
|
||||
]
|
||||
check(E.eq(warning.mouse_filter, Control.MOUSE_FILTER_IGNORE), "Warning overlay does not block input")
|
||||
var environments: Array = [[E.of("1.0e-7"), "ff0000ff"], [E.of("1.0e-6"), "df2020bf"], [E.of("1.0e-5"), "c040407f"], [E.of("0.1"), "00000000"], [E.of("0.9"), "40a0c07f"], [E.of("0.97"), "20b0dfbf"], [1, "00c0ffff"], [E.of("0.1"), "00000000"]]
|
||||
for sample in environments:
|
||||
check(Parameters.density_warning_color(sample[0]).is_equal_approx(Color.html(sample[1])), "Unity warning threshold: " + str(sample[0]))
|
||||
stage.owniverse.state.amount.H = 1e14
|
||||
check(Parameters.density_warning_color(sample[0]).is_equal_approx(Color.html(sample[1])), E.add("Unity warning threshold: ", str(sample[0])))
|
||||
stage.owniverse.state.amount.H = E.of("1e14")
|
||||
stage.world_indicators.update(stage.owniverse)
|
||||
check(warning.self_modulate.is_equal_approx(Color.html("00c0ffff")), "Compression warning uses current universe density")
|
||||
stage.owniverse.state.amount.H = 0.0
|
||||
stage.owniverse.state.amount.S = 1e24
|
||||
stage.owniverse.state.amount.H = 0
|
||||
stage.owniverse.state.amount.S = E.of("1e24")
|
||||
stage.world_indicators.update(stage.owniverse)
|
||||
check(warning.self_modulate.is_equal_approx(Color.html("ff0000ff")), "Rip warning replaces compression warning")
|
||||
stage.owniverse.state.amount.S = 0.0
|
||||
stage.owniverse.state.amount.S = 0
|
||||
stage.world_indicators.update(stage.owniverse)
|
||||
check(warning.self_modulate.a == 0.0, "Warning clears when density returns to normal")
|
||||
check(E.eq(warning.self_modulate.a, 0), "Warning clears when density returns to normal")
|
||||
for bar in stage.world_indicators.energy_bars:
|
||||
check(bar.scale.x == 0.25, "Energy fill is synchronized on both pages")
|
||||
check(E.eq(bar.scale.x, E.of("0.25")), "Energy fill is synchronized on both pages")
|
||||
var pointer: Control = stage.world_indicators.pointers.Bur
|
||||
check(is_equal_approx(pointer.position.x + pointer.size.x / 2.0, 304.0), "Burnability pointer reaches the correct scale endpoint")
|
||||
check(stage.world_indicators.trend_bars[1].scale.y == 0.75, "Trend bar displays its stored level")
|
||||
check(E.near(E.add(pointer.position.x, E.divide(pointer.size.x, 2)), 304), "Burnability pointer reaches the correct scale endpoint")
|
||||
check(E.eq(stage.world_indicators.trend_bars[1].scale.y, E.of("0.75")), "Trend bar displays its stored level")
|
||||
for locale in ["en", "ru"]:
|
||||
global.Locale = locale
|
||||
for id in ["Energy", "Density", "Burnability", "Explosiveness", "Aggression", "Trends"]:
|
||||
var description: String = Descriptions.render(stage.owniverse, global, id, "Indicator")
|
||||
check(not description.contains("—") and not description.contains("%value"), "Resolved indicator description: " + locale + "/" + id)
|
||||
check(((not description.contains("—")) and (not description.contains("%value"))), E.add(E.add(E.add("Resolved indicator description: ", locale), "/"), id))
|
||||
dispose(stage.owniverse)
|
||||
scene.free()
|
||||
print("World indicators: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("World indicators: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
Reference in New Issue
Block a user