Files
owniverse/world/owniverse/manual_production.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

208 lines
9.8 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 QuantityMath = preload("res://world/owniverse/quantity_math.gd")
func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant, automatic: bool = false, physical: Dictionary = {}) -> bool:
if (E.le(amount, 0) or E.ne(amount, E.floor_value(amount))):
return false
var trial: Dictionary = target.duplicate()
trial.amount = target.amount.duplicate()
trial.binds = target.binds.duplicate(true)
trial.auto_resources = target.auto_resources.duplicate()
trial.batches = target.batches.duplicate()
# Physical object totals do not change while resources are withdrawn from
# this transaction. Keep one snapshot instead of scanning every resource.
var shared_physical: bool = not physical.is_empty()
if not shared_physical: physical = world.ledger.physical_amounts(trial)
var next_physical: Dictionary = physical.duplicate() if shared_physical else {}
var copied_batches: Dictionary = {}
for resource in entity.cost:
var cost: Variant = E.round_value(E.multiply(E.maximum(0, entity.cost[resource]), amount))
if (automatic and E.lt(trial.auto_resources.get(resource, 0), cost)):
return false
var taken: Dictionary = world.ledger.take_available(trial, resource, cost)
var total: Variant = 0
for quantity in taken.values():
total = E.add(total, quantity)
if E.ne(total, cost):
return false
if automatic:
trial.auto_resources[resource] = E.maximum(0, E.subtract(trial.auto_resources.get(resource, 0), cost))
for id in taken:
if shared_physical and next_physical.has(id): next_physical[id] = E.subtract(next_physical[id], taken[id])
# Isolate only batches actually touched by this transaction.
if (trial.batches.has(id) and (not copied_batches.has(id))):
trial.batches[id] = trial.batches[id].duplicate(true)
copied_batches[id] = true
var factor: Variant = (E.subtract(1, E.divide(taken[id], physical[id])) if E.gt(physical.get(id, 0), 0) else 0)
world.batches.scale_batches(trial, id, factor)
if (not world.ledger.bind_new_owners(trial, {entity.id: amount}, true)):
return false
world.ledger.refresh_groups(trial)
world.ledger.clamp_auto_resources(trial)
for field in ["amount", "binds", "batches", "auto_resources"]:
if trial.has(field):
target[field] = trial[field]
if shared_physical: physical.merge(next_physical, true)
return true
func smbh_sources() -> Dictionary:
var masses: Dictionary = {}
for id in world.catalog.entities:
var entity: OwniverseEntity = world.catalog.entities[id]
if (E.eq(entity.count_as, "BH") and E.gt(entity.mass, 0)):
masses[id] = E.multiply(E.floor_value(E.maximum(0, world.state.amount.get(id, 0))), entity.mass)
return masses
func smbh_available() -> Variant:
var mass: Variant = 0
for value in smbh_sources().values():
mass = E.add(mass, value)
return E.floor_value(mass)
func plan_smbh(desired_amount: Variant) -> Dictionary:
var empty: Dictionary = {"amount": 0, "taken": {}}
if (E.le(desired_amount, 0) or (not E.finite(desired_amount))):
return empty
var total_mass: Variant = smbh_available()
if E.le(total_mass, 0):
return empty
var requested: Variant = E.minimum(E.floor_value(desired_amount), total_mass)
if E.le(requested, 0):
return empty
var masses: Dictionary = smbh_sources()
var distribution: Dictionary = QuantityMath.normalize_distribution(masses)
var taken: Dictionary = {}
var order: Array = []
var consumed_mass: Variant = 0
for id in distribution:
var expected: Variant = E.mul_div(requested, distribution[id], world.catalog.entities[id].mass)
taken[id] = E.minimum(E.floor_value(expected), E.floor_value(world.state.amount[id]))
consumed_mass = E.add(consumed_mass, E.multiply(taken[id], world.catalog.entities[id].mass))
order.append({"id": id, "remainder": E.subtract(expected, taken[id])})
order.sort_custom(func ( a : Dictionary , b : Dictionary ) -> bool : return (E.gt(a.remainder, b.remainder) if E.ne(a.remainder, b.remainder) else E.lt(str(a.id), str(b.id))))
# Consume whole BH only; all excess mass becomes SMBH, never disappears.
for candidate : Dictionary in order:
if E.ge(consumed_mass, requested):
break
var id: String = candidate.id
if E.lt(taken[id], E.floor_value(world.state.amount[id])):
taken[id] = E.add(taken[id], 1)
consumed_mass = E.add(consumed_mass, world.catalog.entities[id].mass)
if (E.le(consumed_mass, 0) or E.ne(consumed_mass, E.floor_value(consumed_mass))):
return empty
return {"amount": consumed_mass, "taken": taken}
func produce_smbh(desired_amount: Variant) -> Variant:
var plan: Dictionary = plan_smbh(desired_amount)
var consumed_mass: Variant = plan.amount
var taken: Dictionary = plan.taken
if E.le(consumed_mass, 0):
return 0
var next: Dictionary = world.state.duplicate(true)
var physical: Dictionary = world.ledger.physical_amounts(world.state)
for id in taken:
if E.le(taken[id], 0):
continue
next.amount[id] = E.subtract(next.amount[id], taken[id])
var fraction: Variant = E.divide(taken[id], physical[id])
world.batches.scale_batches(next, id, E.subtract(1, fraction))
# Release the consumed owners' share of any resources they held.
for resource in next.binds.get(id, {}):
var released: Variant = E.multiply(next.binds[id][resource], fraction)
next.binds[id][resource] = E.subtract(next.binds[id][resource], released)
next.amount[resource] = E.add(next.amount.get(resource, 0), released)
world.ledger.credit(next, "SMBH", consumed_mass)
world.batches.append_batch(next, "SMBH", consumed_mass, world.state.time.datetime, world.state.time.datetime)
world.ledger.round_object_allocations(next)
world.ledger.refresh_groups(next)
world.ledger.clamp_auto_resources(next)
world.state = next
world.presentation.refresh(world.state, world.catalog)
return consumed_mass
func produce(item_id: String, desired_amount: Variant) -> Variant:
if E.eq(item_id, "SMBH"):
return produce_smbh(desired_amount)
# Use special production for S and H
if (E.eq(item_id, "S") or E.eq(item_id, "H")):
return produce_hs(item_id, desired_amount)
var entity: OwniverseEntity = world.catalog.entities[item_id]
if E.le(desired_amount, 0):
return 0
var max_producible = E.floor_value(calculate_max_producible(entity, desired_amount))
var budget: Dictionary = CivilizationState.stellar_budget(world, world.state, Global.CivilizationRules)
max_producible = CivilizationState.admit_transition(budget, Global.CivilizationRules, "", item_id, max_producible)
if E.le(max_producible, 0):
return 0
var metallicity: Variant = world.environment.stellar_environment(world.state).metallicity
if (not try_production(world.state, entity, max_producible)):
return 0
# Add produced entities
world.ledger.process_entity(item_id, max_producible)
world.batches.add_entity_to_batch(item_id, max_producible, world.state.time.datetime)
world.stellar.create_planets(world.state, item_id, max_producible, world.state.time.datetime, world.state.time.datetime, metallicity)
world.ledger.refresh_groups(world.state)
world.presentation.refresh(world.state, world.catalog)
return max_producible
func produce_hs(entity_id: String, value: Variant) -> Variant:
if E.le(value, 0):
return 0
var to_produce: Variant = E.floor_value(value)
to_produce = E.minimum(to_produce, E.floor_value(world.state.energy))
if E.le(to_produce, 0):
return 0
world.state.energy = E.subtract(world.state.energy, to_produce)
world.ledger.process_entity(entity_id, to_produce)
return to_produce
func production_step(entity: OwniverseEntity) -> Variant:
return QuantityMath.production_step(E.add(entity.cost.values(), entity.bind.values()))
func calculate_max_producible(entity: OwniverseEntity, desired_amount: Variant = null) -> Variant:
if E.eq(entity.id, "SMBH"):
return plan_smbh(smbh_available() if desired_amount == null else desired_amount).amount
var requirements: Dictionary = {}
for resource in entity.cost:
requirements[resource] = E.maximum(0, entity.cost[resource])
for resource in entity.bind:
requirements[resource] = E.add(requirements.get(resource, 0), E.maximum(0, entity.bind[resource]))
var has_requirements: bool = false
for quantity in requirements.values():
if E.gt(quantity, 0):
has_requirements = true
break
# No recipe means unavailable for production, not unlimited free objects.
if (not has_requirements):
return 0
var available: Variant = INF if desired_amount == null else E.maximum(0, desired_amount)
for resource in requirements:
if E.gt(requirements[resource], 0):
available = E.minimum(available, E.floor_value(E.divide(world.ledger.available(world.state, resource), requirements[resource])))
var concrete: Dictionary = world.ledger.expand_requirements(world.state, requirements)
for resource in concrete:
if E.gt(concrete[resource], 0):
available = E.minimum(available, E.floor_value(E.divide(world.ledger.available(world.state, resource), concrete[resource])))
var step: Variant = production_step(entity)
return (E.multiply(E.floor_value(E.divide(available, step)), step) if E.finite(step) else 0)