Implement civilization flow mechanics and profiling for resource allocation
- Added `civilization_flow.gd` to manage continuous expected flows and resource allocation within civilizations. - Introduced `allocate_profiled` and `whole_quotas` functions in `quantity_math.gd` for precise stock allocation based on float shares. - Enhanced `credit` logic in `object_ledger.gd` to handle production remainders more effectively. - Updated `append_batch` in `object_batches.gd` to prevent batch creation for invalid IDs. - Introduced `ratio_approx` function in `exact.gd` for better handling of float profiles. - Added a benchmark script `benchmark_civilization_flows.gd` to evaluate the performance of civilization flow mechanics. - Refactored various functions to improve clarity and efficiency in handling resource flows and allocations.
This commit is contained in:
@@ -1,164 +1,136 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
const State = preload("res://world/owniverse/civilization_state.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
func _initialize(): call_deferred("run")
|
||||
func check(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
var global: Node = root.get_node("Global")
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
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
|
||||
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
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
for id in ["RD", "OD", "YD", "WYD", "BG", "TWD", "SNS", "MGT", "BH25", "SMBH", "SCD", "SCS", "SCM", "SCL", "SCG", "GED", "GES", "GEM", "GEL", "GEG", "LGG", "LSC", "LGF", "LGW", "LUB"]:
|
||||
if world.state.amount.has(id): world.state.amount[id] = E.of("1e60")
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
for entity in world.catalog.entities.values():
|
||||
entity.free()
|
||||
func dispose(world):
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
|
||||
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 population(world) -> Variant:
|
||||
var result: Variant = 0
|
||||
for id in world.ledger.physical_amounts(world.state):
|
||||
if root.get_node("Global").CivilizationRules.has(id) or world.catalog.entities[id].group_as == "Planet":
|
||||
result = E.add(result, world.ledger.physical_amount(world.state, id))
|
||||
return result
|
||||
|
||||
func invariants(world) -> void:
|
||||
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
for id in physical:
|
||||
check(E.ge(physical[id], 0) and E.eq(physical[id], E.floor_value(physical[id])), "Physical counts remain nonnegative whole: " + id)
|
||||
for id in world.state.ethics:
|
||||
var total: float = 0.0
|
||||
for n in world.state.ethics[id].values():
|
||||
var share: float = 0.0 + n
|
||||
if not is_finite(share) or share < 0.0 or share > 1.0: check(false, "Invalid ethic share")
|
||||
total += share
|
||||
check(absf(total - 1.0) < 0.000001, E.add("Ethic shares sum to one for ", id))
|
||||
check(E.ge(physical.get(id, 0), 0), E.add("Exact physical population remains available for ", id))
|
||||
check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 6, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Civilization state validates")
|
||||
for value in world.state.ethics[id].values():
|
||||
check(is_finite(value) and value >= 0.0 and value <= 1.0, "Finite normalized ethic shares")
|
||||
total += value
|
||||
check(absf(total - 1.0) < 1e-12, "Ethic shares sum to one: " + id)
|
||||
|
||||
func run() -> void:
|
||||
func configure(rules: Dictionary, progress: float, degradation: float, span: int = 100) -> void:
|
||||
for id in rules:
|
||||
if rules[id].ethics.is_empty(): continue
|
||||
rules[id].span = span
|
||||
rules[id].fights = false
|
||||
rules[id].colonizes = false
|
||||
rules[id].diplomacy = false
|
||||
for row in rules[id].ethics.values():
|
||||
row.progress = progress
|
||||
row.degradation = degradation
|
||||
row.war = 0
|
||||
row.colonization = 0
|
||||
row.fall = rules[id].previous + ":1"
|
||||
|
||||
func run():
|
||||
var global: Node = root.get_node("Global")
|
||||
var baseline: Dictionary = global.CivilizationRules.duplicate(true)
|
||||
configure(global.CivilizationRules, 1.0, 0.0)
|
||||
var world = fixture()
|
||||
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")
|
||||
var count: Variant = E.of("10000000000000000000000000000000000000007")
|
||||
world.state.amount["C0.2"] = count
|
||||
world.state.ethics["C0.2"] = State.empty_profile()
|
||||
world.civilization_flow.evolve(world.state, 0, 100)
|
||||
check(E.eq(population(world), count), "Continuous chain conserves arbitrary-size whole population")
|
||||
check(absf(E.approx(E.divide(world.state.amount["C0.2"], count)) - exp(-1.0)) < 1e-12, "Survival follows the exponential interval law")
|
||||
check(E.gt(world.state.amount["C0.4"], 0), "One interval includes multiple successive stages")
|
||||
check(not world.state.batches.has("C0.2") and not world.state.batches.has("C0.3"), "Civilizations do not create age batches")
|
||||
invariants(world)
|
||||
var restored = fixture()
|
||||
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)
|
||||
var half = fixture()
|
||||
half.state.amount["C0.2"] = count
|
||||
half.state.ethics["C0.2"] = State.empty_profile()
|
||||
half.civilization_flow.evolve(half.state, 0, 50)
|
||||
half.civilization_flow.evolve(half.state, 50, 100)
|
||||
for id in global.CivilizationRules:
|
||||
check(E.approx(E.divide(E.absolute(E.subtract(world.state.amount.get(id, 0), half.state.amount.get(id, 0))), count)) < 1e-12, "Splitting the interval preserves continuous dynamics: " + id)
|
||||
dispose(half)
|
||||
dispose(world)
|
||||
|
||||
configure(global.CivilizationRules, 0.0, 0.0)
|
||||
world = fixture()
|
||||
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)
|
||||
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")
|
||||
world.state.amount["C0.2"] = 10001
|
||||
world.state.ethics["C0.2"] = {"m": 1.0, "l": 0.0, "w": 0.0, "d": 0.0, "c": 0.0, "b": 0.0}
|
||||
world.civilization_flow.evolve(world.state, 0, 100)
|
||||
check(E.eq(world.state.amount["C0.2"], 10001), "Disabled stage movement retains every individual")
|
||||
check(world.state.ethics["C0.2"].m < 1.0 and world.state.ethics["C0.2"].m > 0.9, "Analytical drift changes shares without materializing populations")
|
||||
invariants(world)
|
||||
dispose(world)
|
||||
|
||||
configure(global.CivilizationRules, 1.0, 0.0)
|
||||
world = fixture()
|
||||
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(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 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")
|
||||
for id in ["RD", "OD", "YD", "WYD"]: world.state.amount[id] = 0
|
||||
world.state.amount["C1.9"] = 101
|
||||
world.civilization_flow.evolve(world.state, 0, 100)
|
||||
check(E.eq(world.state.amount["C1.9"], 101), "Unfunded interstellar flows return to their source")
|
||||
check(E.eq(population(world), 101), "Resource admission conserves exact population")
|
||||
dispose(world)
|
||||
|
||||
configure(global.CivilizationRules, 0.0, 0.0, 1)
|
||||
world = fixture()
|
||||
world.state.amount["C1.6"] = 60000
|
||||
world.state.binds.RD = {"HP": 5, "RP": 5, "GG": 5, "AB": 5}
|
||||
global.CivilizationRules["C1.6"].colonizes = true
|
||||
for row in global.CivilizationRules["C1.6"].ethics.values(): row.colonization = 1
|
||||
world.civilization_flow.evolve(world.state, 0, 1)
|
||||
check(E.eq(world.state.civilization_stats.colonies, 20), "Integrated colonization is limited by real planet stock")
|
||||
check(E.eq(world.state.binds.RD.get("C1.4", 0), 20), "Colonies retain the target stellar owner")
|
||||
check(E.eq(population(world), 60020), "Colonization creates precisely the consumed number of occupied worlds")
|
||||
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
for resource in ["HP", "RP", "GG", "AB"]: check(E.eq(physical[resource], 0), "Planet stock consumed once: " + resource)
|
||||
invariants(world)
|
||||
dispose(world)
|
||||
|
||||
global.CivilizationRules = baseline.duplicate(true)
|
||||
world = fixture()
|
||||
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 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 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():
|
||||
row.progress = "1"
|
||||
row.degradation = "0"
|
||||
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")
|
||||
world.state.amount["C2.0"] = 60000
|
||||
world.state.amount["C1.9"] = 10000
|
||||
world.civilization_flow.evolve(world.state, 0, 20000)
|
||||
check(E.gt(world.state.civilization_stats.wars, 0), "War rewards integrate encounters during the interval")
|
||||
check(E.gt(world.state.civilization_stats.collateral, 0), "Collateral damage reaches previous stages")
|
||||
check(E.eq(population(world), 70000), "War outcomes and collateral conserve occupied-world counts")
|
||||
invariants(world)
|
||||
var restored = fixture()
|
||||
restored.state = E.decode(E.encode(world.state))
|
||||
world.civilization_flow.evolve(world.state, 20000, 30000)
|
||||
restored.civilization_flow.evolve(restored.state, 20000, 30000)
|
||||
check(E.eq(world.state, restored.state), "Save resume reproduces flow populations and carries")
|
||||
dispose(restored)
|
||||
dispose(world)
|
||||
global.CivilizationRules = baseline
|
||||
|
||||
world = fixture()
|
||||
world.state.amount.RD = 10000
|
||||
world.state.amount.SCS = 1
|
||||
world.state.amount["C2.4"] = 1
|
||||
world.evolution.initialize_life_batches()
|
||||
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, "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(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 = E.of("1e15")
|
||||
world.state.amount.BG = E.of("1e15")
|
||||
world.state.amount["C3.0"] = 6
|
||||
world.evolution.initialize_life_batches()
|
||||
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 E.eq(failures, 0) else 1))
|
||||
print("Civilization flows: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
|
||||
Reference in New Issue
Block a user