Refactor Owniverse modules for quantity mathematics and entity catalog
- Introduced `quantity_math.gd` for arithmetic operations including allocation, normalization, and accumulation of quantities without scene tree dependencies. - Created `entity_catalog.gd` to manage entity definitions, groups, and event indexes, ensuring separation from simulation state and UI. - Implemented `item_presentation.gd` for read-only projections of simulation state, handling amounts and unlock conditions for UI elements. - Updated `civilization_evolution.gd` and `civilization_state.gd` to utilize new quantity math functions, improving code clarity and maintainability. - Enhanced tests in `test_quantity_catalog.gd` to validate new functionality and ensure correctness of mathematical operations. - Revised documentation to reflect changes in module structure and functionality.
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
# Проверки поэтапного переноса из Owniverse.U
|
||||
|
||||
## Математика распределений и каталог
|
||||
|
||||
`godot --headless --path Owniverse.GI --script res://tests/test_quantity_catalog.gd`
|
||||
|
||||
Проверяются целочисленность и границы распределения, сохранение суммы, разрешение равных остатков, накопление дробей и долгов округления, нормализация весов, размер производственной партии, загрузка каталога и его индексы. Архитектура описана в `../world/owniverse/README.md`. Сохранение игрока не используется.
|
||||
|
||||
## Иконки стадий жизни
|
||||
|
||||
Тест также проверяет модуль `item_presentation.gd`: отсутствие изменений входного состояния и списка закрытых иконок, решения по разблокировке, отложенную и принудительную публикацию количеств.
|
||||
|
||||
`godot --headless --path Owniverse.GI --script res://tests/test_life_icons.gd`
|
||||
|
||||
Проверяются одновременная разблокировка подробных и общих иконок EON/LP и C0.0/C0, количества свободных и связанных планет без двойного учёта, переключение подпанелей и очистка счётчика при исчезновении стадии. Сохранение игрока не используется.
|
||||
|
||||
@@ -22,6 +22,7 @@ func run() -> void:
|
||||
manager.save_completed.connect(func(filename, success, error): completions.append([filename, success, error]))
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
var data: Dictionary = {"state": world.state, "ui": {"current_page": 1}}
|
||||
var initial_energy: float = world.state.energy
|
||||
var global: Node = root.get_node("Global")
|
||||
var old_plaintext: bool = global.save_plaintext_copy
|
||||
global.save_plaintext_copy = true
|
||||
@@ -35,9 +36,9 @@ func run() -> void:
|
||||
await process_frame
|
||||
check(not manager.is_saving() and frames > 0, "Frames continue while save is pending")
|
||||
var saved: Dictionary = manager.load_data("snapshot", "test-password")
|
||||
check(saved.state.energy == 1e14 and saved.ui.current_page == 1, "Worker owns a deep snapshot")
|
||||
check(saved.state.energy == initial_energy and saved.ui.current_page == 1, "Worker owns a deep snapshot")
|
||||
var plain: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("snapshot.adventure.json")))
|
||||
check(plain.state.energy == 1e14, "Diagnostic JSON uses same snapshot")
|
||||
check(plain.state.energy == initial_energy, "Diagnostic JSON uses same snapshot")
|
||||
check(completions.size() == 1 and completions[0][1], "Completion reports successful write")
|
||||
|
||||
completions.clear()
|
||||
|
||||
@@ -33,6 +33,23 @@ func run() -> void:
|
||||
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.get_item_amounts(true).EON1 == 12.0 and world.get_item_amounts(true).LP == 16.0, "Historical counts retain individual and aggregate values")
|
||||
check(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")
|
||||
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")
|
||||
var snapshot: Dictionary = world.items_to_update.duplicate()
|
||||
world.defer_item_updates = true
|
||||
world.state.amount.EON1 += 1.0
|
||||
world.refresh_item_amounts()
|
||||
check(world.item_amounts_dirty and world.items_to_update == snapshot, "Deferred refresh retains pending snapshot")
|
||||
world.refresh_item_amounts(true)
|
||||
check(not world.item_amounts_dirty and world.items_to_update.EON1 == 13.0, "Forced refresh publishes latest amounts")
|
||||
world.state.amount.EON1 -= 1.0
|
||||
world.defer_item_updates = false
|
||||
world.refresh_item_amounts()
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
extends SceneTree
|
||||
|
||||
const Math = preload("res://world/owniverse/quantity_math.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
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")
|
||||
for limit in range(30):
|
||||
var requests: Dictionary = {"a": 3.9, "b": 9.1, "c": -2.0, "d": 7.0}
|
||||
var result: Dictionary = Math.allocate_whole(requests, limit)
|
||||
check(Math.sum(result) == minf(limit, 19.0), "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")
|
||||
var target: Dictionary = {}
|
||||
var produced: float = 0.0
|
||||
for i in 10:
|
||||
produced += Math.accumulate_whole(target, "remainders", "a", 0.1)
|
||||
check(produced == 1.0, "Fractional production carries")
|
||||
var remainders: Dictionary = {}
|
||||
var totals: Dictionary = {"a": 0.0, "b": 0.0, "c": 0.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 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")
|
||||
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(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(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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bvri1enpsjisk
|
||||
+2
-1
@@ -70,7 +70,8 @@ func run() -> void:
|
||||
world.evolute(0.005)
|
||||
check(world.state.time.year == 0.1, "Speed change preserves accumulated fraction")
|
||||
world.speed = 100
|
||||
check(world.speed == 5 and world.state.speed_in_year == 10000.0, "Upper speed limit unchanged")
|
||||
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")
|
||||
dispose(world)
|
||||
|
||||
Reference in New Issue
Block a user