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
+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