Add new tests and implement frame budget management
- Introduced `civilization_benchmark.gd` and `legacy_quantity_math.gd` for benchmarking and legacy quantity calculations. - Created `test_dynamic_math.gd`, `test_frame_evolution.gd`, and `test_sparse_batches.gd` to validate mathematical operations and frame evolution logic. - Implemented `frame_budget.gd` to manage cooperative work on the main thread, ensuring no partially calculated state is published. - Added UID files for all new scripts to maintain unique identifiers.
This commit is contained in:
@@ -44,12 +44,11 @@ static func detach_space_civilizations(target: Dictionary, rules: Dictionary) ->
|
||||
static func stellar_budget(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||
if physical.is_empty():
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
var layout: Dictionary = world.catalog.civilization_layout(rules)
|
||||
var capacity: Dictionary = {}
|
||||
for id in physical:
|
||||
var entity = world.catalog.entities[id]
|
||||
for home in ["DwStar", "GiStar", "WD", "NS", "MGT", "BH"]:
|
||||
if ((E.eq(entity.event_group, home) or E.eq(entity.count_as, home)) or E.eq(id, home)):
|
||||
capacity[home] = E.add(capacity.get(home, 0), physical[id])
|
||||
for id in layout.star_homes:
|
||||
for home in layout.star_homes[id]:
|
||||
capacity[home] = E.add(capacity.get(home, 0), physical.get(id, 0))
|
||||
capacity["SMBH"] = E.maximum(0, target.amount.get("SMBH", 0))
|
||||
for held : Dictionary in target.binds.values():
|
||||
capacity["SMBH"] = E.add(capacity["SMBH"], E.maximum(0, held.get("SMBH", 0)))
|
||||
@@ -106,22 +105,21 @@ static func admit_transition(budget: Dictionary, rules: Dictionary, source: Stri
|
||||
# Resolve shortages from the most advanced stages downwards. This gives existing
|
||||
# advanced populations priority and lets a demotion cascade in the same cycle.
|
||||
static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year: Variant) -> void:
|
||||
var profile_started: int = world.profile_start()
|
||||
detach_space_civilizations(target, rules)
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
reconcile(world, target, rules, physical)
|
||||
var budget: Dictionary = stellar_budget(world, target, rules, physical)
|
||||
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):
|
||||
stages.append(id)
|
||||
stages.sort()
|
||||
var stages: Array = world.catalog.civilization_layout(rules).stages
|
||||
var changed: bool = false
|
||||
for index in range(E.subtract(stages.size(), 1), E.neg(1), E.neg(1)):
|
||||
var id: String = stages[index]
|
||||
var count: Variant = target.amount.get(id, 0)
|
||||
if E.le(count, 0): continue
|
||||
var home: String = rules[id].home
|
||||
var cost: Variant = E.maximum(1, rules[id].stars)
|
||||
var count: Variant = target.amount.get(id, 0)
|
||||
var supported: Variant = E.minimum(count, E.floor_value(E.divide(remaining.get(home, 0), cost)))
|
||||
var requirement: Dictionary = budget.habitats.requirements.get(id, {})
|
||||
if (not requirement.is_empty()):
|
||||
@@ -131,6 +129,7 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
|
||||
var excess: Variant = E.subtract(count, supported)
|
||||
if E.le(excess, 0):
|
||||
continue
|
||||
changed = true
|
||||
var previous: String = (stages[E.subtract(index, 1)] if E.gt(index, 0) else "C1.9")
|
||||
world.batches.scale_batches(target, id, E.divide(supported, count))
|
||||
target.amount[id] = supported
|
||||
@@ -161,79 +160,71 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
|
||||
target.binds[owner] = {}
|
||||
target.binds[owner][previous] = E.add(target.binds[owner].get(previous, 0), amount)
|
||||
target.amount[previous] = E.subtract(target.amount[previous], amount)
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
reconcile(world, target, rules, physical)
|
||||
if changed:
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
reconcile(world, target, rules, physical)
|
||||
assign_homes(world, target, rules, physical)
|
||||
world.profile_end("enforce_capacity", profile_started)
|
||||
|
||||
# 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, physical: Dictionary = {}) -> Dictionary:
|
||||
if physical.is_empty():
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
var structures: Array = []
|
||||
var layout: Dictionary = world.catalog.civilization_layout(rules)
|
||||
var structures: Array = layout.structures
|
||||
var capacity: Dictionary = {}
|
||||
for id in world.catalog.entities:
|
||||
if E.ne(world.catalog.entities[id].claim, ""):
|
||||
structures.append(id)
|
||||
capacity[id] = physical.get(id, 0)
|
||||
structures.sort_custom(func ( a , b ) : return (E.gt(world.catalog.entities[a].civ_bonus, world.catalog.entities[b].civ_bonus) if E.ne(world.catalog.entities[a].civ_bonus, world.catalog.entities[b].civ_bonus) else E.lt(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: Variant = 1
|
||||
for id in stages:
|
||||
var direct: String = ""
|
||||
for structure in structures:
|
||||
if E.eq(world.catalog.entities[structure].claim, id): direct = structure
|
||||
if E.ne(direct, ""):
|
||||
anchor = direct
|
||||
units = 1
|
||||
else:
|
||||
units = E.multiply(units, 10)
|
||||
anchors[id] = anchor
|
||||
if (id.begins_with("C3.") or id.begins_with("C4.")):
|
||||
requirements[id] = {"id": anchor, "units": units}
|
||||
for id in structures: capacity[id] = physical.get(id, 0)
|
||||
var requirements: Dictionary = layout.requirements
|
||||
var anchors: Dictionary = layout.anchors
|
||||
var requested: Dictionary = {}
|
||||
for id in requirements:
|
||||
var req: Dictionary = requirements[id]
|
||||
requested[req.id] = E.add(requested.get(req.id, 0), E.multiply(physical.get(id, 0), req.units))
|
||||
return {"capacity": capacity, "requested": requested, "requirements": requirements, "structures": structures, "anchors": anchors}
|
||||
|
||||
# A structure's direct reservation belongs only to its Claim stage. Other
|
||||
# stages do not debit it, so each stage's placement can be calculated alone.
|
||||
static func stage_homes(world, target: Dictionary, id: String, layout: Dictionary, capacity: Dictionary = {}) -> Dictionary:
|
||||
var left: Variant = target.amount.get(id, 0)
|
||||
var homes: Dictionary = {}
|
||||
if E.le(left, 0): return homes
|
||||
var allowed: bool = layout.anchors[id] == ""
|
||||
for structure in layout.structures:
|
||||
if structure == layout.anchors[id]: allowed = true
|
||||
if not allowed: continue
|
||||
var stock: Variant = capacity.get(structure, 0) if not capacity.is_empty() else world.ledger.physical_amount(target, structure)
|
||||
if E.le(stock, 0): continue
|
||||
var amount: Variant = left
|
||||
if world.catalog.entities[structure].claim == id:
|
||||
amount = E.minimum(amount, stock)
|
||||
if E.gt(amount, 0): homes[structure] = amount
|
||||
left = E.subtract(left, amount)
|
||||
if E.le(left, 0): break
|
||||
return homes
|
||||
|
||||
static func assign_homes(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> void:
|
||||
var budget: Dictionary = habitat_budget(world, target, rules, physical)
|
||||
var used: Dictionary = {}
|
||||
var profile_started: int = world.profile_start()
|
||||
var layout: Dictionary = world.catalog.civilization_layout(rules)
|
||||
var capacity: Dictionary = {}
|
||||
for structure in layout.structures:
|
||||
capacity[structure] = world.ledger.physical_amount(target, structure) if physical.is_empty() else physical.get(structure, 0)
|
||||
target.civilization_homes = {}
|
||||
var stages: Array = budget.anchors.keys()
|
||||
stages.sort()
|
||||
stages.reverse()
|
||||
for id in stages:
|
||||
var left: Variant = target.amount.get(id, 0)
|
||||
var homes: Dictionary = {}
|
||||
var allowed: bool = E.eq(budget.anchors[id], "")
|
||||
for structure in budget.structures:
|
||||
if E.eq(structure, budget.anchors[id]): allowed = true
|
||||
if ((not allowed) or E.le(budget.capacity[structure], 0)): continue
|
||||
var amount: Variant = left
|
||||
if E.eq(world.catalog.entities[structure].claim, id):
|
||||
amount = E.minimum(amount, E.maximum(0, E.subtract(budget.capacity[structure], used.get(structure, 0))))
|
||||
used[structure] = E.add(used.get(structure, 0), amount)
|
||||
if E.gt(amount, 0): homes[structure] = amount
|
||||
left = E.subtract(left, amount)
|
||||
if E.le(left, 0): break
|
||||
target.civilization_homes[id] = homes
|
||||
for index in range(layout.stages.size() - 1, -1, -1):
|
||||
var id: String = layout.stages[index]
|
||||
target.civilization_homes[id] = stage_homes(world, target, id, layout, capacity)
|
||||
world.profile_end("assign_homes", profile_started)
|
||||
|
||||
|
||||
static func effective_span(world, target: Dictionary, id: String, rules: Dictionary) -> Variant:
|
||||
if (not rules.has(id)): return world.catalog.entities[id].lifespan
|
||||
var base: Variant = rules[id].span
|
||||
if (not is_space_civilization(id, rules)): return base
|
||||
assign_homes(world, target, rules)
|
||||
var population: Variant = target.amount.get(id, 0)
|
||||
if E.le(population, 0): return base
|
||||
var homes: Dictionary = stage_homes(world, target, id, world.catalog.civilization_layout(rules))
|
||||
target.civilization_homes[id] = homes
|
||||
var weighted: Variant = population
|
||||
for home in target.civilization_homes.get(id, {}):
|
||||
weighted = E.add(weighted, E.multiply(target.civilization_homes[id][home], (E.subtract(E.maximum(0, world.catalog.entities[home].civ_bonus), 1))))
|
||||
for home in homes:
|
||||
weighted = E.add(weighted, E.multiply(homes[home], (E.subtract(E.maximum(0, world.catalog.entities[home].civ_bonus), 1))))
|
||||
return (E.mul_div(base, population, weighted) if E.gt(weighted, 0) else base)
|
||||
|
||||
Reference in New Issue
Block a user