Add comprehensive civilization evolution tests and enhance civilization state management

- Introduced a new test script for civilization evolution (`test_civilization_evolution.gd`) to validate the evolution mechanics, including population dynamics, ethical transitions, and war impacts.
- Updated `README.md` to include instructions for running the new civilization evolution tests.
- Modified `test_civilization_state.gd` to accommodate changes in civilization rules and ensure proper validation of ethical stages.
- Enhanced `test_descriptions.gd` to check for accurate population counters and state validation.
- Adjusted `test_life.gd` to reflect updates in life version management.
- Improved `civilization_state.gd` to include habitat budget calculations and transition management.
- Added `civilization_evolution.gd` to handle the logic for civilization evolution, including population drift, warfare, and colonization mechanics.
- Updated `owniverse.gd` and `owniverse_entity.gd` to integrate new evolution logic and ensure proper initialization of entities.
This commit is contained in:
Kirill
2026-09-16 19:54:47 +03:00
parent 5ea65a7e9b
commit 26347fca30
14 changed files with 607 additions and 20 deletions
+249
View File
@@ -0,0 +1,249 @@
extends RefCounted
const State = preload("res://world/owniverse/civilization_state.gd")
const ETHICS = State.ETHICS
const OPPOSITE = {"m": "l", "l": "m", "w": "d", "d": "w", "c": "b", "b": "c"}
static func total(pop: Dictionary) -> float:
var result: float = 0.0
for n in pop.values(): result += n
return result
static func add(to: Dictionary, from: Dictionary) -> void:
for key in from: to[key] = to.get(key, 0.0) + from[key]
static func take(world, pop: Dictionary, count: float) -> Dictionary:
var taken: Dictionary = world._allocate_whole(pop, count)
for key in taken: pop[key] -= taken[key]
return taken
static func rate(target: Dictionary, key: String, count: float, probability: float) -> float:
if not target.has("civilization_remainders"): target.civilization_remainders = {}
var expected: float = count * maxf(0.0, probability) + target.civilization_remainders.get(key, 0.0)
var actual: float = floorf(snappedf(expected, 1e-10))
target.civilization_remainders[key] = maxf(0.0, expected - actual)
return actual
# Fixed-size largest-remainder apportionment with saved signed rounding errors.
# Runtime depends on the number of branches, never the number of civilizations.
static func split(target: Dictionary, key: String, count: float, weights: Dictionary) -> Dictionary:
var result: Dictionary = {}
var sum_weights: float = total(weights)
if count <= 0.0 or sum_weights <= 0.0: return result
if not target.has("civilization_remainders"): target.civilization_remainders = {}
var expected: Dictionary = {}
var positive_sum: float = 0.0
for id in weights:
expected[id] = count * weights[id] / sum_weights + target.civilization_remainders.get(key + ":" + id, 0.0)
positive_sum += maxf(0.0, expected[id])
var order: Array = []
var assigned: float = 0.0
for id in weights:
var normalized: float = maxf(0.0, expected[id]) * count / positive_sum if positive_sum > 0.0 else 0.0
result[id] = floorf(normalized)
assigned += result[id]
order.append({"id": id, "rest": normalized - result[id]})
order.sort_custom(func(a, b): return a.rest > b.rest if a.rest != b.rest else str(a.id) < str(b.id))
for entry in order:
if assigned >= count: break
result[entry.id] += 1.0
assigned += 1.0
for id in weights:
target.civilization_remainders[key + ":" + id] = clampf(expected[id] - result[id], -1.0, 1.0)
return result
static func drift(target: Dictionary, key: String, pop: Dictionary, rows: Dictionary,
diplomacy: bool = false, colony: bool = false) -> Dictionary:
var weights: Dictionary = {}
for ethic in pop:
var influence: float = float(rows[ethic].diplomacy_rate) if diplomacy else 1.0
for other in ETHICS:
var weight: float = 9.0 if colony else 1.0
if other == ethic: weight = 960.0 if colony else 196.0
elif other == OPPOSITE[ethic]: weight = 4.0 if colony else 0.0
weights[other] = weights.get(other, 0.0) + pop[ethic] * influence * weight
if total(weights) <= 0.0: return pop.duplicate()
return split(target, key, total(pop), weights)
static func emit(plan: Dictionary, destination: String, pop: Dictionary) -> void:
if not plan.outputs.has(destination): plan.outputs[destination] = {}
add(plan.outputs[destination], pop)
static func fall(target: Dictionary, plan: Dictionary, rules: Dictionary, pop: Dictionary) -> void:
for ethic in pop:
var weights: Dictionary = {}
for term in str(rules[plan.id].ethics[ethic].fall).split("^", false):
var pair: PackedStringArray = term.split(":")
weights[pair[0]] = float(pair[1])
if weights.is_empty(): weights[rules[plan.id].previous] = 1.0
var shares: Dictionary = split(target, "fall:" + plan.id + ":" + ethic, pop[ethic], weights)
for stage in shares: emit(plan, stage, {ethic: shares[stage]})
static func progress(world, target: Dictionary, rules: Dictionary, budget: Dictionary,
plan: Dictionary, pop: Dictionary) -> Dictionary:
var successor: String = rules[plan.id].next
var admitted: float = State.admit_transition(budget, rules, plan.id, successor, total(pop))
var accepted: Dictionary = take(world, pop, admitted)
emit(plan, successor, accepted)
return pop
static func new_plan(id: String, year: float) -> Dictionary:
return {"id": id, "from": year, "to": -INF, "amount": 0.0, "outputs": {}}
static func consume_untriggered(world, target: Dictionary, plan: Dictionary, pop: Dictionary, count: float, year: float) -> Dictionary:
var old_total: float = total(pop)
var removed: Dictionary = take(world, pop, count)
var actual: float = total(removed)
if actual > 0.0:
world._scale_batches(target, plan.id, (old_total - actual) / old_total)
plan.amount += actual
plan.to = maxf(plan.to, year)
return removed
static func prepare(world, target: Dictionary, expired: Dictionary, year: float, budget: Dictionary, rules: Dictionary) -> Dictionary:
State.reconcile(world, target, rules)
var remaining: Dictionary = target.ethics.duplicate(true)
var plans: Dictionary = {}
var colonies: Array = []
var collateral: Dictionary = {}
if not target.has("civilization_stats"): target.civilization_stats = {}
var ids: Array = expired.keys()
ids.sort()
for id in ids:
if not rules.has(id) or expired[id].is_empty(): continue
var plan: Dictionary = new_plan(id, year)
plans[id] = plan
for part: Dictionary in expired[id]:
plan.amount += part.amount
plan.from = minf(plan.from, part.from)
plan.to = maxf(plan.to, part.to)
var triggered: Dictionary = take(world, remaining[id], plan.amount)
var successes: Dictionary = {}
var failures: Dictionary = {}
var warriors: Dictionary = {}
var settlers: Dictionary = {}
for ethic in triggered:
var row: Dictionary = rules[id].ethics[ethic]
var p: float = maxf(0.0, float(row.progress))
var d: float = maxf(0.0, float(row.degradation))
var scale: float = maxf(1.0, p + d)
var pro: float = rate(target, id + ":" + ethic + ":pro", triggered[ethic], p / scale)
var deg: float = rate(target, id + ":" + ethic + ":deg", triggered[ethic], d / scale)
var bounded: Dictionary = world._allocate_whole({"pro": pro, "deg": deg}, triggered[ethic])
if rules[id].fights: warriors[ethic] = rate(target, id + ":" + ethic + ":war", triggered[ethic], float(row.war))
if rules[id].colonizes: settlers[ethic] = rate(target, id + ":" + ethic + ":col", triggered[ethic], float(row.colonization))
successes[ethic] = bounded.pro
failures[ethic] = bounded.deg
triggered[ethic] -= bounded.pro + bounded.deg
add(triggered, progress(world, target, rules, budget, plan, successes))
fall(target, plan, rules, failures)
if rules[id].fights:
for ethic in warriors: warriors[ethic] = minf(warriors[ethic], triggered[ethic])
var wars: float = minf(total(warriors), floorf((total(triggered) + total(remaining[id])) / 2.0))
warriors = world._allocate_whole(warriors, wars)
for ethic in warriors: triggered[ethic] -= warriors[ethic]
var outcomes: Dictionary = split(target, id + ":wars", wars, {"deg": 1.0, "pro25": 2.0, "pro50": 3.0, "pro75": 3.0})
var win_expected: float = outcomes.get("pro25", 0.0) * 0.25 + outcomes.get("pro50", 0.0) * 0.5 + outcomes.get("pro75", 0.0) * 0.75
var loss_expected: float = outcomes.get("deg", 0.0) * 2.0 + outcomes.get("pro25", 0.0) + outcomes.get("pro50", 0.0) * 1.5 + outcomes.get("pro75", 0.0) * 2.0
var winners: Dictionary = take(world, warriors, rate(target, id + ":wins", win_expected, 1.0))
var attackers_lost: Dictionary = take(world, warriors, outcomes.get("deg", 0.0))
add(triggered, progress(world, target, rules, budget, plan, winners))
fall(target, plan, rules, attackers_lost)
var losses: float = minf(rate(target, id + ":loss", loss_expected, 1.0), total(triggered) + total(remaining[id]))
var sides: Dictionary = world._allocate_whole({"due": total(triggered), "later": total(remaining[id])}, losses)
var victims: Dictionary = take(world, triggered, sides.due)
add(victims, consume_untriggered(world, target, plan, remaining[id], sides.later, year))
fall(target, plan, rules, victims)
add(triggered, warriors)
var damage: float = outcomes.get("pro50", 0.0) * 10.0 + outcomes.get("pro75", 0.0) * 30.0
collateral[rules[id].previous] = collateral.get(rules[id].previous, 0.0) + damage
target.civilization_stats.wars = target.civilization_stats.get("wars", 0.0) + wars
emit(plan, id, triggered)
if total(settlers) > 0.0: colonies.append({"stage": rules[id].colony, "source": id, "population": settlers})
# Collateral is applied after all stages planned their own attempts.
for id in collateral:
if not rules.has(id): continue
if not plans.has(id): plans[id] = new_plan(id, year)
var plan: Dictionary = plans[id]
var retry: Dictionary = plan.outputs.get(id, {})
var amount: float = minf(collateral[id], total(retry) + total(remaining[id]))
var sides: Dictionary = world._allocate_whole({"due": total(retry), "later": total(remaining[id])}, amount)
var victims: Dictionary = take(world, retry, sides.due)
add(victims, consume_untriggered(world, target, plan, remaining[id], sides.later, year))
fall(target, plan, rules, victims)
target.civilization_stats.collateral = target.civilization_stats.get("collateral", 0.0) + total(victims)
var incoming: Dictionary = {}
var planetary: Dictionary = {}
for id in plans:
var plan: Dictionary = plans[id]
var products: Dictionary = {}
for destination in plan.outputs:
var pop: Dictionary = plan.outputs[destination]
if rules.has(destination):
if destination != "Ct":
pop = drift(target, id + ":" + destination + ":drift", pop, rules[id].ethics, destination == id and rules[id].diplomacy)
if not incoming.has(destination): incoming[destination] = {}
add(incoming[destination], pop)
products[destination] = total(pop)
if State.is_space_civilization(id, rules) and world.entities[destination].group_as == "Planet":
planetary[destination] = planetary.get(destination, 0.0) + total(pop)
expired[id] = [{"amount": plan.amount, "from": plan.from, "to": maxf(plan.from, plan.to), "products": products}]
return {"remaining": remaining, "incoming": incoming, "planetary": planetary, "colonies": colonies}
static func finish(world, target: Dictionary, transaction: Dictionary, year: float, rules: Dictionary) -> void:
target.ethics = transaction.remaining
for id in transaction.incoming:
if not target.ethics.has(id): target.ethics[id] = {}
add(target.ethics[id], transaction.incoming[id])
# Space civilizations that fall back to planetary stages need a stellar host.
var physical: Dictionary = world._physical_amounts(target)
var hosts: Dictionary = {}
for id in physical:
if world.entities[id].event_group == "DwStar" and physical[id] > 0.0: hosts[id] = physical[id]
for id in transaction.planetary:
var shares: Dictionary = split(target, "return:" + id, minf(transaction.planetary[id], target.amount.get(id, 0.0)), hosts)
for owner in shares:
if not target.binds.has(owner): target.binds[owner] = {}
target.binds[owner][id] = target.binds[owner].get(id, 0.0) + shares[owner]
target.amount[id] -= shares[owner]
colonize(world, target, transaction.colonies, year, rules)
State.reconcile(world, target, rules)
var worship: float = 0.0
for id in rules:
if not rules[id].worships: continue
for ethic in target.ethics[id]: worship += target.ethics[id][ethic] * float(rules[id].ethics[ethic].worship)
target.civilization_stats.worship = worship
static func colonize(world, target: Dictionary, colonies: Array, year: float, rules: Dictionary) -> void:
var physical: Dictionary = world._physical_amounts(target)
var stock: Dictionary = {}
for id in ["GG", "AB", "RP", "HP"]: stock[id] = physical.get(id, 0.0)
var requests: Dictionary = {}
for i in colonies.size(): requests[str(i)] = total(colonies[i].population)
var granted: Dictionary = world._allocate_whole(requests, total(stock))
for i in colonies.size():
var colony: Dictionary = colonies[i]
var population: Dictionary = world._allocate_whole(colony.population, granted[str(i)])
population = drift(target, colony.source + ":colonies", population, rules[colony.source].ethics, false, true)
var count: float = total(population)
var targets: Dictionary = world._allocate_whole(stock, count)
for id in targets:
var amount: float = targets[id]
if amount <= 0.0: continue
var slots: Dictionary = {"": target.amount.get(id, 0.0)}
for owner in target.binds: slots[owner] = target.binds[owner].get(id, 0.0)
var taken: Dictionary = world._allocate_whole(slots, amount)
world._scale_batches(target, id, 1.0 - amount / stock[id])
stock[id] -= amount
for owner in taken:
if taken[owner] <= 0.0: continue
if owner == "": target.amount[id] -= taken[owner]
else: target.binds[owner][id] -= taken[owner]
var created: float = world._credit(target, colony.stage, taken[owner])
if owner != "":
target.amount[colony.stage] -= created
target.binds[owner][colony.stage] = target.binds[owner].get(colony.stage, 0.0) + created
world._append_batch(target, colony.stage, amount, year, year)
if not target.ethics.has(colony.stage): target.ethics[colony.stage] = {}
add(target.ethics[colony.stage], population)
target.civilization_stats.colonies = target.civilization_stats.get("colonies", 0.0) + count
@@ -0,0 +1 @@
uid://cypckxhb2n2r8
+93 -1
View File
@@ -59,7 +59,7 @@ static func stellar_budget(world, target: Dictionary, rules: Dictionary) -> Dict
var free: Dictionary = {}
for home in capacity:
free[home] = maxf(0.0, capacity[home] - requested.get(home, 0.0))
return {"capacity": capacity, "requested": requested, "free": free, "populations": populations}
return {"capacity": capacity, "requested": requested, "free": free, "populations": populations, "habitats": habitat_budget(world, target, rules)}
# Account for the source's existing reservation when changing stages. A failed
# transition retains its population at the source, rather than losing objects.
@@ -81,6 +81,17 @@ static func admit_transition(budget: Dictionary, rules: Dictionary, source: Stri
var admitted: float = floorf(maxf(0.0, requested))
if net_cost > 0.0:
admitted = minf(admitted, floorf(budget.free.get(home, 0.0) / net_cost))
if budget.has("habitats"):
var housing: Dictionary = budget.habitats
var new_req: Dictionary = housing.requirements.get(destination, {})
var old_req: Dictionary = housing.requirements.get(source, {})
if not new_req.is_empty():
var net_units: float = new_req.units - (old_req.units if old_req.get("id", "") == new_req.id else 0.0)
if net_units > 0.0:
var free_units: float = maxf(0.0, housing.capacity.get(new_req.id, 0.0) - housing.requested.get(new_req.id, 0.0))
admitted = minf(admitted, floorf(free_units / net_units))
if not old_req.is_empty(): housing.requested[old_req.id] = maxf(0.0, housing.requested.get(old_req.id, 0.0) - admitted * old_req.units)
if not new_req.is_empty(): housing.requested[new_req.id] = housing.requested.get(new_req.id, 0.0) + admitted * new_req.units
if old_home != "":
budget.requested[old_home] = maxf(0.0, budget.requested.get(old_home, 0.0) - admitted * old_cost)
budget.free[old_home] = maxf(0.0, budget.capacity.get(old_home, 0.0) - budget.requested[old_home])
@@ -95,6 +106,7 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
reconcile(world, target, rules)
var budget: Dictionary = stellar_budget(world, target, rules)
var remaining: Dictionary = budget.capacity.duplicate()
var remaining_homes: Dictionary = budget.habitats.capacity.duplicate()
var stages: Array = []
for id in rules:
if is_space_civilization(id, rules):
@@ -106,6 +118,10 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
var cost: float = maxf(1.0, rules[id].stars)
var count: float = target.amount.get(id, 0.0)
var supported: float = minf(count, floorf(remaining.get(home, 0.0) / cost))
var requirement: Dictionary = budget.habitats.requirements.get(id, {})
if not requirement.is_empty():
supported = minf(supported, floorf(remaining_homes.get(requirement.id, 0.0) / requirement.units))
remaining_homes[requirement.id] = maxf(0.0, remaining_homes.get(requirement.id, 0.0) - supported * requirement.units)
remaining[home] = maxf(0.0, remaining.get(home, 0.0) - supported * cost)
var excess: float = count - supported
if excess <= 0.0:
@@ -141,3 +157,79 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
target.binds[owner][previous] = target.binds[owner].get(previous, 0.0) + amount
target.amount[previous] -= amount
reconcile(world, target, rules)
assign_homes(world, target, rules)
# Unity's structure chain: direct Claim resets the required structure count;
# intervening stages require ten times more units of the preceding structure.
static func habitat_budget(world, target: Dictionary, rules: Dictionary) -> Dictionary:
var physical: Dictionary = world._physical_amounts(target)
var structures: Array = []
var capacity: Dictionary = {}
for id in world.entities:
if world.entities[id].claim != "":
structures.append(id)
capacity[id] = physical.get(id, 0.0)
structures.sort_custom(func(a, b):
var left: float = world.entities[a].civ_bonus
var right: float = world.entities[b].civ_bonus
return left > right if left != right else str(a) < str(b))
var stages: Array = []
for id in rules:
if is_space_civilization(id, rules): stages.append(id)
stages.sort()
var requirements: Dictionary = {}
var anchors: Dictionary = {}
var anchor: String = ""
var units: float = 1.0
for id in stages:
var direct: String = ""
for structure in structures:
if world.entities[structure].claim == id: direct = structure
if direct != "":
anchor = direct
units = 1.0
else:
units *= 10.0
anchors[id] = anchor
if id.begins_with("C3.") or id.begins_with("C4."):
requirements[id] = {"id": anchor, "units": units}
var requested: Dictionary = {}
for id in requirements:
var req: Dictionary = requirements[id]
requested[req.id] = requested.get(req.id, 0.0) + physical.get(id, 0.0) * req.units
return {"capacity": capacity, "requested": requested, "requirements": requirements, "structures": structures, "anchors": anchors}
static func assign_homes(world, target: Dictionary, rules: Dictionary) -> void:
var budget: Dictionary = habitat_budget(world, target, rules)
var used: Dictionary = {}
target.civilization_homes = {}
var stages: Array = budget.anchors.keys()
stages.sort()
stages.reverse()
for id in stages:
var left: float = target.amount.get(id, 0.0)
var homes: Dictionary = {}
var allowed: bool = budget.anchors[id] == ""
for structure in budget.structures:
if structure == budget.anchors[id]: allowed = true
if not allowed or budget.capacity[structure] <= 0.0: continue
var amount: float = left
if world.entities[structure].claim == id:
amount = minf(amount, maxf(0.0, budget.capacity[structure] - used.get(structure, 0.0)))
used[structure] = used.get(structure, 0.0) + amount
if amount > 0.0: homes[structure] = amount
left -= amount
if left <= 0.0: break
target.civilization_homes[id] = homes
static func effective_span(world, target: Dictionary, id: String, rules: Dictionary) -> float:
if not rules.has(id): return world.entities[id].lifespan
var base: float = rules[id].span
if not is_space_civilization(id, rules): return base
assign_homes(world, target, rules)
var population: float = target.amount.get(id, 0.0)
if population <= 0.0: return base
var weighted: float = population
for home in target.civilization_homes.get(id, {}):
weighted += target.civilization_homes[id][home] * (maxf(0.0, world.entities[home].civ_bonus) - 1.0)
return base * population / weighted if weighted > 0.0 else base
+21 -6
View File
@@ -1,5 +1,6 @@
extends Node
class_name Owniverse
const CivilizationEvolution = preload("res://world/owniverse/civilization_evolution.gd")
const CivilizationState = preload("res://world/owniverse/civilization_state.gd")
const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
@@ -67,7 +68,7 @@ func migrate_time_precision() -> void:
func initialize_life_batches() -> void:
CivilizationState.detach_space_civilizations(state, Global.CivilizationRules)
CivilizationState.enforce_capacity(self, state, Global.CivilizationRules, state.time.datetime)
if state.get("life_version", 0) >= 2:
if state.get("life_version", 0) >= 3:
return
var physical: Dictionary = _physical_amounts(state)
for id in entities:
@@ -77,7 +78,7 @@ func initialize_life_batches() -> void:
for batch: Dictionary in state.batches.get(id, []):
tracked += batch.amount
_append_batch(state, id, maxf(0.0, physical.get(id, 0.0) - tracked), state.time.datetime, state.time.datetime)
state.life_version = 2
state.life_version = 3
new_state = state
@@ -111,6 +112,15 @@ func _evolute_to_datetime(datetime: float) -> void:
## Aggregate only for presentation; never write aliases back into simulation state.
func get_item_amounts(total_produced: bool = false) -> Dictionary[String, float]:
var source: Dictionary = state.stats.total_amount if total_produced else state.amount
if not total_produced:
source = source.duplicate()
var physical: Dictionary = _physical_amounts(state)
var planets: float = 0.0
for id in physical:
if entities[id].group_as == "Planet":
source[id] = physical[id]
planets += physical[id]
source["Planet"] = planets
var result: Dictionary[String, float] = {}
for id in source:
result[id] = 0.0
@@ -317,8 +327,8 @@ func _append_batch(target: Dictionary, id: String, amount: float,
if not target.has("batch_origins"):
target.batch_origins = {}
var batches: Array = target.batches[id]
var lifespan: float = entities[id].lifespan
var batch_duration: float = entities[id].lifespan_delta
var lifespan: float = CivilizationState.effective_span(self, target, id, Global.CivilizationRules)
var batch_duration: float = lifespan / Global.batch_count if lifespan > 10.0 else lifespan
if batch_duration <= 0.0:
return
if not target.batch_origins.has(id):
@@ -344,7 +354,7 @@ func _append_batch(target: Dictionary, id: String, amount: float,
part_amount = floorf(amount * (part_end - created_from) / duration) - (amount - remaining_amount)
var batch: Dictionary = {}
for existing: Dictionary in batches:
if existing.get("created_from", -INF) == window_start:
if existing.get("created_from", -INF) == window_start and existing.start_year == window_start + lifespan and existing.end_year == window_end + lifespan:
batch = existing
break
if batch.is_empty():
@@ -637,6 +647,7 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
var fractions: Dictionary = {}
var outputs: Dictionary = {}
var star_budget: Dictionary = CivilizationState.stellar_budget(self, target, Global.CivilizationRules)
var civilization_transaction: Dictionary = CivilizationEvolution.prepare(self, target, expired, to_year, star_budget, Global.CivilizationRules)
for id in expired:
var total: float = 0.0
for part: Dictionary in expired[id]:
@@ -644,8 +655,11 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
fractions[id] = clampf(total / quantities[id], 0.0, 1.0) if quantities.get(id, 0.0) > 0.0 else 0.0
var combined: Dictionary = {}
for part: Dictionary in expired[id]:
part.products = _evolution_products(target, entities[id], part.amount)
if not part.has("products"):
part.products = _evolution_products(target, entities[id], part.amount)
for successor in part.products.keys():
if Global.CivilizationRules.has(id):
continue
var requested: float = part.products[successor]
var admitted: float = CivilizationState.admit_transition(star_budget, Global.CivilizationRules, id, successor, requested)
part.products[successor] = admitted
@@ -719,6 +733,7 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
target.amount[id] = maxf(0.0, target.amount.get(id, 0.0) - transferred)
_bind_new_owners(target, new_owners)
_round_object_allocations(target)
CivilizationEvolution.finish(self, target, civilization_transaction, to_year, Global.CivilizationRules)
_apply_blast(target, blast_power, environment)
CivilizationState.enforce_capacity(self, target, Global.CivilizationRules, to_year)
return true
+2
View File
@@ -44,6 +44,8 @@ func _init(values: Dictionary) -> void:
mass = float(values.mass)
space = int(values.space)
life_rule = Global.LifeRules.get(id, {}).duplicate(true)
if Global.CivilizationRules.has(id):
life_rule = {"ethical": true, "span": Global.CivilizationRules[id].span}
lifespan = float(life_rule.span) if not life_rule.is_empty() else float(values.lifespan)
lifespan_delta = lifespan / Global.batch_count if lifespan > 10 else lifespan
auto = float(values.auto)