Refactor civilization evolution and state management

- Enhanced the `civilization_evolution.gd` script to improve population handling and resource allocation during civilization transitions.
- Introduced new functions in `civilization_state.gd` for profile normalization and merging, ensuring consistent population representation across stages.
- Updated `evolution_coordinator.gd` to incorporate budget phases for better tracking of simulation performance.
- Improved `frame_budget.gd` to monitor maximum slice durations and phases during processing.
- Added a new benchmarking tool in `android_benchmark.gd` to measure performance metrics during simulation runs, including frame processing times and throughput.
- Created associated scene and resource files for the benchmarking tool to facilitate testing and performance evaluation.
This commit is contained in:
Kirill
2026-10-06 23:02:42 +03:00
parent e48d4bbe88
commit 6ee66a627d
23 changed files with 751 additions and 3009 deletions
+68 -21
View File
@@ -5,11 +5,68 @@ const QuantityMath = preload("res://world/owniverse/quantity_math.gd")
const ETHICS = ["m", "l", "w", "d", "c", "b"]
static func empty_profile() -> Dictionary:
var result: Dictionary = {}
for ethic in ETHICS: result[ethic] = 1.0 / ETHICS.size()
return result
static func normalize_profile(profile: Dictionary) -> Dictionary:
var result: Dictionary = {}
var total: float = 0.0
for ethic in ETHICS:
var value: float = maxf(0.0, E.approx(profile.get(ethic, 0.0)))
result[ethic] = value
total += value
if not is_finite(total) or total <= 0.0: return empty_profile()
if profile.size() == ETHICS.size() and absf(total - 1.0) <= 0.000000000001:
return result
var assigned: float = 0.0
for i in ETHICS.size() - 1:
var ethic: String = ETHICS[i]
result[ethic] = (0.0 + result[ethic]) / total
assigned += result[ethic]
result[ETHICS.back()] = maxf(0.0, 1.0 - assigned)
return result
static func profile_from_counts(population: Dictionary) -> Dictionary:
var total: Variant = QuantityMath.sum(population)
if E.le(total, 0): return empty_profile()
var profile: Dictionary = {}
for ethic in ETHICS:
profile[ethic] = E.approx(E.divide(population.get(ethic, 0), total))
return normalize_profile(profile)
static func materialize_profile(target: Dictionary, id: String, profile: Dictionary, count: Variant) -> Dictionary:
var normalized: Dictionary = normalize_profile(profile)
if not target.has("civilization_remainders"): target.civilization_remainders = {}
var shares: Array = []
for ethic in ETHICS: shares.append(normalized[ethic])
var result: Dictionary = QuantityMath.split_profile(target.civilization_remainders, "profile:" + id, count, ETHICS, shares)
for ethic in ETHICS:
if not result.has(ethic): result[ethic] = 0
return result
static func merge_profile(base: Dictionary, base_count: Variant, incoming: Dictionary) -> Dictionary:
var incoming_count: Variant = QuantityMath.sum(incoming)
return merge_profiles(base, base_count, profile_from_counts(incoming), incoming_count)
static func merge_profiles(base: Dictionary, base_count: Variant, incoming_profile: Dictionary, incoming_count: Variant) -> Dictionary:
var total: Variant = E.add(base_count, incoming_count)
if E.le(total, 0): return empty_profile()
var base_ratio: float = E.approx(E.divide(base_count, total)) if E.gt(base_count, 0) else 0.0
var incoming_ratio: float = E.approx(E.divide(incoming_count, total)) if E.gt(incoming_count, 0) else 0.0
var normalized_base: Dictionary = normalize_profile(base)
var result: Dictionary = {}
for ethic in ETHICS:
var incoming_share: float = E.approx(incoming_profile.get(ethic, 0.0)) if E.gt(incoming_count, 0) else 0.0
result[ethic] = (0.0 + normalized_base[ethic]) * base_ratio + incoming_share * incoming_ratio
return normalize_profile(result)
static func is_space_civilization(id: String, rules: Dictionary) -> bool:
return (rules.has(id) and E.ne(str(rules[id].home), ""))
# Preserve any existing ethics. New populations start equally distributed;
# reductions (including blasts) are proportional and never leave negative counts.
# Keep one six-value float profile per stage. Absolute population remains in
# the exact physical object ledger and is materialized only when a transition needs it.
static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}, only_ids: Array = []) -> void:
if (not target.has("ethics")):
target.ethics = {}
@@ -19,22 +76,10 @@ static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Di
for id in ids:
if not rules.has(id): continue
var count: Variant = physical.get(id, 0)
var current: Dictionary = target.ethics.get(id, {})
var current: Dictionary = normalize_profile(target.ethics.get(id, {}))
var cached: Dictionary = world.ledger.reconciled_ethics.get(id, {})
if not cached.is_empty() and E.eq(cached.count, count) and E.eq(cached.population, current):
continue
var total: Variant = 0
for ethic in ETHICS:
current[ethic] = E.maximum(0, current.get(ethic, 0))
total = E.add(total, current[ethic])
if E.gt(total, count):
current = QuantityMath.allocate_whole(current, count)
elif E.lt(total, count):
var missing: Variant = E.subtract(count, total)
var base: Variant = E.floor_value(E.divide(missing, ETHICS.size()))
var rest: int = E.integer(E.subtract(missing, E.multiply(base, ETHICS.size())))
for i in ETHICS.size():
current[ETHICS[i]] = E.add(current[ETHICS[i]], E.add(base, ((1 if E.lt(i, rest) else 0))))
target.ethics[id] = current
world.ledger.reconciled_ethics[id] = {"count": count, "population": current.duplicate()}
@@ -115,6 +160,7 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
detach_space_civilizations(target, rules)
var physical: Dictionary = world.ledger.physical_amounts(target)
reconcile(world, target, rules, physical)
var ethical_totals: Dictionary = physical.duplicate()
var budget: Dictionary = stellar_budget(world, target, rules, physical)
var remaining: Dictionary = budget.capacity.duplicate()
var remaining_homes: Dictionary = budget.habitats.capacity.duplicate()
@@ -141,12 +187,13 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
target.amount[id] = supported
world.ledger.credit(target, previous, excess)
world.batches.append_batch(target, previous, excess, year, year)
var removed: Dictionary = QuantityMath.allocate_whole(target.ethics[id], excess)
if (not target.ethics.has(previous)):
target.ethics[previous] = {}
for ethic in ETHICS:
target.ethics[id][ethic] = E.subtract(target.ethics[id][ethic], removed[ethic])
target.ethics[previous][ethic] = E.add(target.ethics[previous].get(ethic, 0), removed[ethic])
var previous_count: Variant = ethical_totals.get(previous, physical.get(previous, target.amount.get(previous, 0)))
var previous_profile: Dictionary = target.ethics.get(previous, empty_profile())
var source_profile: Dictionary = target.ethics.get(id, empty_profile())
target.ethics[previous] = merge_profiles(previous_profile, previous_count, source_profile, excess)
ethical_totals[id] = supported
ethical_totals[previous] = E.add(previous_count, excess)
# Capacity losses are proportional, so the surviving source keeps its profile.
if E.eq(previous, "C1.9"):
var stars: Dictionary = {}
var current_physical: Dictionary = world.ledger.physical_amounts(target)