c42940369a
- 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.
237 lines
14 KiB
GDScript
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
|