Files
owniverse/world/owniverse/evolution_coordinator.gd
T
Kirill c42940369a Refactor capacity enforcement and production logic
- Updated `enforce_capacity` function to accept an optional `physical` parameter, allowing for more flexible handling of physical amounts.
- Modified `initialize_life_batches` to return physical amounts after enforcing capacity, improving data flow.
- Enhanced `try_production` to utilize shared physical amounts, reducing redundant calculations.
- Adjusted `stellar_environment` to accept an optional `quantities` parameter, streamlining its usage.
- Added new test results for physical totals to validate performance improvements across various scenarios.
2026-10-08 21:10:36 +03:00

237 lines
14 KiB
GDScript

extends "res://world/owniverse/simulation_module.gd"
const E = preload("res://world/owniverse/math/exact.gd")
const CivilizationState = preload("res://world/owniverse/civilization_state.gd")
const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
const QuantityMath = preload("res://world/owniverse/quantity_math.gd")
func initialize_life_batches() -> Dictionary:
for id in Global.CivilizationRules:
world.state.batches.erase(id)
world.state.batch_origins.erase(id)
CivilizationState.detach_space_civilizations(world.state, Global.CivilizationRules)
var physical: Dictionary = CivilizationState.enforce_capacity(world, world.state, Global.CivilizationRules, world.state.time.datetime)
for id in world.catalog.entities:
if world.catalog.entities[id].life_rule.is_empty():
continue
var tracked: Variant = 0
for batch : Dictionary in world.state.batches.get(id, []):
tracked = E.add(tracked, batch.amount)
world.batches.append_batch(world.state, id, E.maximum(0, E.subtract(physical.get(id, 0), tracked)), world.state.time.datetime, world.state.time.datetime)
return physical
func run_frame(clock: Dictionary, budget: RefCounted) -> void:
var _keep_alive: RefCounted = self
await evolute_to_datetime(clock.datetime, clock, budget)
if not budget.cancelled and world != null: world.finish_frame()
func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefCounted = null) -> void:
var _keep_alive: RefCounted = self
# Reconcile only when simulation advances.
# Fractional frame updates cannot change capacity or populations.
var processed_year: Variant = SimulationTime.processed_year(datetime)
var quantities: Dictionary = {}
if E.gt(processed_year, world.state.time.year):
if budget != null: budget.call("set_phase", "initialize_life_batches")
var profile_initialize: int = world.profile_start()
quantities = initialize_life_batches()
world.profile_end("initialize", profile_initialize)
var previous: Dictionary = world.state
if budget != null: budget.call("set_phase", "deep_copy_and_coarsen")
var next: Dictionary = previous.duplicate_deep()
next.version = SimulationTime.STATE_VERSION
next.speed = clampi(E.integer(previous.speed), 1, Global.SPEED_LIMIT)
next.speed_in_year = E.power(10, E.subtract(next.speed, 1))
world.batches.coarsen(next)
var from_year: Variant = previous.time.year
var to_year: Variant = processed_year
if E.gt(to_year, from_year):
var distribution: Dictionary = QuantityMath.normalize_distribution(quantities)
next.distribution = distribution
var profile_production: int = world.profile_start()
world.automatic.produce_for_period(previous, next, quantities, distribution, from_year, to_year)
world.profile_end("production", profile_production)
if budget != null:
budget.call("set_phase", "civilization_flows")
await world.civilization_flow.evolve(next, from_year, to_year, budget)
if budget.cancelled: return
else:
world.civilization_flow.evolve(next, from_year, to_year)
var profile_generations: int = world.profile_start()
if budget != null:
await evolve_batches(next, to_year, budget)
if budget.cancelled: return
else:
evolve_batches(next, to_year)
world.profile_end("generations", profile_generations)
world.ledger.refresh_groups(next)
world.ledger.clamp_auto_resources(next)
var profile_trends: int = world.profile_start()
WorldParameters.advance(previous, next, Global.CivilizationRules, E.subtract(to_year, from_year))
world.profile_end("trends", profile_trends)
next.time = clock if not clock.is_empty() else {"datetime": datetime, "year": to_year}
# Publish only after every phase has finished; previous stays untouched.
world.state = E.bound_state(next)
if E.gt(to_year, from_year):
world.presentation.refresh(world.state, world.catalog)
func evolve_batches(target: Dictionary, to_year: Variant, budget: RefCounted = null) -> void:
var _keep_alive: RefCounted = self
# A long cycle may contain several generations; preserve actual birth intervals.
var completed: Dictionary = {}
var context: Dictionary = {
"life_modifiers": WorldParameters.life_modifiers(target),
"enlightenment": WorldParameters.value(target, "Enlightenment"),
"explosiveness": WorldParameters.effective(target, "Exp")
}
while await evolve_batch_generation(target, to_year, completed, budget, context):
if budget != null and not await budget.pause(): return
func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Dictionary = { }, budget: RefCounted = null, context: Dictionary = {}) -> bool:
var _keep_alive: RefCounted = self
# Apply direct completions at their original generation boundaries, so
# binding transfers, blast environment and capacity checks keep order.
if budget != null and not await budget.pause(): return false
if budget != null: budget.call("set_phase", "generation_setup_and_withdrawal")
var completed_now: Dictionary = completed.duplicate()
completed.clear()
if context.is_empty():
context = {"life_modifiers": WorldParameters.life_modifiers(target), "enlightenment": WorldParameters.value(target, "Enlightenment"), "explosiveness": WorldParameters.effective(target, "Exp")}
var quantities: Dictionary = world.ledger.physical_amounts(target)
var expired: Dictionary = {}
var withdrawn: Dictionary = {}
var batch_ids: Array = []
for id in target.batches:
if not Global.CivilizationRules.has(id): batch_ids.append(id)
var planets_changed: bool = false
var profile_withdraw: int = world.profile_start()
for id in batch_ids:
if ((not world.catalog.entities.has(id)) or E.le(world.catalog.entities[id].destroy_level, 0)):
continue
var dying: Variant = 0
for part : Dictionary in completed_now.get(id, []):
dying = E.add(dying, part.amount)
withdrawn[id] = world.batches.withdraw_batches(target, id, to_year)
for part : Dictionary in withdrawn[id]:
dying = E.add(dying, part.amount)
if E.gt(quantities.get(id, 0), 0):
planets_changed = world.stellar.destroy_planets(target, id, E.divide(dying, quantities[id]), world.catalog.entities[id].destroy_level) or planets_changed
world.profile_end("generation_withdrawal", profile_withdraw)
if planets_changed: quantities = world.ledger.physical_amounts(target)
# Withdraw all types before creating successors: iteration order cannot affect them.
for id in batch_ids:
if world.catalog.entities.has(id):
var parts: Array = withdrawn[id] if withdrawn.has(id) else world.batches.withdraw_batches(target, id, to_year)
expired[id] = E.add(completed_now.get(id, []), parts)
var has_expired: bool = false
for parts : Array in expired.values():
if (not parts.is_empty()):
has_expired = true
break
if (not has_expired):
return false
# Aggregate quantities are used to calculate the type-level transfer ratio;
# transition products remain calculated for each expiring cohort.
var expired_totals: Dictionary = {}
for id in expired:
var total: Variant = 0
for part: Dictionary in expired[id]: total = E.add(total, part.amount)
expired_totals[id] = total
var fractions: Dictionary = {}
var outputs: Dictionary = {}
var current_quantities: Dictionary = quantities
if budget != null: budget.call("set_phase", "civilization_stellar_budget")
var star_budget: Dictionary = CivilizationState.stellar_budget(world, target, Global.CivilizationRules, current_quantities)
if budget != null: budget.call("set_phase", "civilization_prepare")
var profile_products: int = world.profile_start()
for id in expired:
if budget != null and not await budget.pause(): return false
var total: Variant = expired_totals[id]
fractions[id] = (E.clamp_value(E.divide(total, quantities[id]), 0, 1) if E.gt(quantities.get(id, 0), 0) else 0)
outputs[id] = {}
var combined: Dictionary = {}
for part: Dictionary in expired[id]:
if not part.has("products"):
part.products = world.stellar.evolution_products(target, world.catalog.entities[id], part.amount, context)
for successor in part.products.keys():
if Global.CivilizationRules.has(id): continue
var requested: Variant = part.products[successor]
var admitted: Variant = CivilizationState.admit_transition(star_budget, Global.CivilizationRules, id, successor, requested)
part.products[successor] = admitted
if E.lt(admitted, requested):
part.products[id] = E.add(part.products.get(id, 0), E.subtract(requested, admitted))
for successor in part.products:
combined[successor] = E.add(combined.get(successor, 0), part.products[successor])
if E.gt(total, 0):
for successor in combined:
outputs[id][successor] = E.divide(combined[successor], total)
world.profile_end("generation_products", profile_products)
var affected: Dictionary = {}
for id in fractions:
if E.le(fractions[id], 0): continue
affected[id] = true
for held in target.binds.get(id, {}): affected[held] = true
for successor in outputs[id]: affected[successor] = true
if budget != null: budget.call("set_phase", "transfer_evolution")
var incoming: Dictionary = world.ledger.transfer_evolution(target, fractions, outputs)
var new_owners: Dictionary = {}
var born_civilizations: Dictionary = {}
var blast_power: Variant = 0
for parts: Array in expired.values():
for part: Dictionary in parts:
blast_power = E.add(blast_power, part.products.get("blast", 0))
var environment: Dictionary = world.environment.stellar_environment(target) if E.gt(blast_power, 0) else {}
var resolution: Variant = world.batches.temporal_resolution(target)
if budget != null: budget.call("set_phase", "transfer_and_newborns")
for id in expired:
for part: Dictionary in expired[id]:
for successor in part.products:
if budget != null and not await budget.pause(): return false
var amount: Variant = part.products[successor]
if E.le(amount, 0) or E.eq(successor, "blast") or not world.catalog.entities.has(successor): continue
if E.eq(successor, id) and (not world.catalog.entities[id].life_rule.is_empty() or Global.CivilizationRules.has(id)):
target.amount[id] = E.add(target.amount.get(id, 0), amount)
else:
amount = world.ledger.credit(target, successor, amount)
var produces: bool = not world.automatic.yearly_products(world.catalog.entities[successor]).is_empty()
var newborn_adjustment: Dictionary = {}
var cutoff: Variant = to_year if world.stellar.uses_short_lived_fast_path(successor) else E.neg(INF)
var born_from: Variant = part.from
var born_to: Variant = part.to
if E.gt(resolution, 0) and (Global.CivilizationRules.has(successor) or not world.catalog.entities[successor].life_rule.is_empty()):
born_to = E.multiply(E.ceil_value(E.divide(born_to, resolution)), resolution)
born_from = born_to
var profile_newborn: int = world.profile_start()
var integral: Variant
if budget != null:
integral = await world.batches.append_batch(target, successor, amount, born_from, born_to, completed, cutoff, budget, newborn_adjustment if produces else null, to_year)
if budget.cancelled: return false
else:
integral = world.batches.append_batch(target, successor, amount, born_from, born_to, completed, cutoff, null, newborn_adjustment if produces else null, to_year)
if produces:
world.automatic.produce_newborn_lifetime(target, successor, amount, born_from, born_to, to_year, newborn_adjustment.get("past_years", 0), integral)
world.profile_end("newborn_batches", profile_newborn)
new_owners[successor] = E.add(new_owners.get(successor, 0), amount)
if Global.CivilizationRules.has(successor): born_civilizations[successor] = E.add(born_civilizations.get(successor, 0), amount)
world.ledger.accept_transfers(target, incoming)
if budget != null: budget.call("set_phase", "finish_and_capacity")
world.ledger.bind_new_owners(target, new_owners)
for owner in new_owners:
for resource in world.catalog.entities[owner].bind:
for member in world.catalog.members(resource): affected[member] = true
world.ledger.round_object_allocations(target, affected)
for id in born_civilizations:
var current: Variant = world.ledger.physical_amount(target, id)
var born: Variant = born_civilizations[id]
target.ethics[id] = CivilizationState.merge_profiles(target.ethics.get(id, {}), E.maximum(0, E.subtract(current, born)), CivilizationState.empty_profile(), born)
if E.gt(blast_power, 0): world.stellar.apply_blast(target, blast_power, environment)
CivilizationState.enforce_capacity(world, target, Global.CivilizationRules, to_year)
return true