Implement civilization flow mechanics and profiling for resource allocation

- Added `civilization_flow.gd` to manage continuous expected flows and resource allocation within civilizations.
- Introduced `allocate_profiled` and `whole_quotas` functions in `quantity_math.gd` for precise stock allocation based on float shares.
- Enhanced `credit` logic in `object_ledger.gd` to handle production remainders more effectively.
- Updated `append_batch` in `object_batches.gd` to prevent batch creation for invalid IDs.
- Introduced `ratio_approx` function in `exact.gd` for better handling of float profiles.
- Added a benchmark script `benchmark_civilization_flows.gd` to evaluate the performance of civilization flow mechanics.
- Refactored various functions to improve clarity and efficiency in handling resource flows and allocations.
This commit is contained in:
Kirill
2026-10-07 22:51:55 +03:00
parent 6ee66a627d
commit 3fcc7211ce
41 changed files with 247959 additions and 817 deletions
+25 -24
View File
@@ -35,7 +35,7 @@ func run():
direct = fixture()
sliced = fixture()
var initial_h: Variant = sliced.state.amount.H
sliced.evolute_frame(1.0 / 60.0, self)
sliced.evolute_frame(0.2, self)
check(sliced.frame_busy, "Heavy transaction yields before publishing")
check(E.eq(sliced.state.time.year, 0), "Published clock stays at completed transaction")
sliced.request_production("H", 1, true)
@@ -45,7 +45,7 @@ func run():
sliced.speed = 3
check(sliced.speed == 3 and sliced.state.speed == 6, "Speed change is visible and cannot mutate an unfinished transaction")
while sliced.frame_busy: await process_frame
direct.evolute(1.0 / 60.0)
direct.evolute(0.2)
direct.production.produce_hs("H", 1)
direct.speed = 3
check(E.eq(direct.state, sliced.state), "Queued action and speed change survive publication")
@@ -60,7 +60,7 @@ func run():
dispose(direct)
dispose(sliced)
var original = fixture()
original.evolute_frame(1.0 / 60.0, self)
original.evolute_frame(0.2, self)
original.request_production("H", 123, true)
original.evolute_frame(0.01, self)
original.speed = 3
@@ -84,7 +84,7 @@ func run():
dispose(resumed)
dispose(expected)
var cancelled = fixture()
cancelled.evolute_frame(0.133333333333, self)
cancelled.evolute_frame(0.2, self)
check(cancelled.frame_busy, "Cancellation fixture has suspended work")
var budget_ref: RefCounted = cancelled.frame_budget
dispose(cancelled)
@@ -96,25 +96,26 @@ func run():
invalid.evolute(1e308)
check(not invalid.frame_busy and invalid.frame_clock.is_empty() and E.eq(before, invalid.state), "Overflowing elapsed float cannot enter either clock API")
dispose(invalid)
var adaptive = fixture()
adaptive.frame_clock = Clock.advance(adaptive.state.time, 10.0, 6)
var debt_clock: Dictionary = adaptive.frame_clock.duplicate(true)
check(adaptive.update_frame_step() == 0.2, "Growing debt increases step to 0.2 seconds")
check(adaptive.update_frame_step() == 0.5, "Large debt increases step to bounded 0.5 seconds")
check(adaptive.update_frame_step() == 0.5 and E.eq(adaptive.frame_clock, debt_clock), "Maximum step preserves requested time")
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.3, 6)
check(adaptive.update_frame_step() == 0.5, "Hysteresis keeps step between thresholds")
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.05, 6)
check(adaptive.update_frame_step() == 0.2, "Small debt reduces step")
check(adaptive.update_frame_step() == 0.1, "Caught-up simulation returns to base step")
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.15, 6)
adaptive._frame_cost_ratio = 1.1
check(adaptive.update_frame_step() == 0.2, "Measured slow transactions increase step before two-step debt")
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.05, 6)
adaptive.speed = 3
check(adaptive.update_frame_step() == 1.0 / 60.0, "Low speeds retain 60 Hz scheduling")
adaptive.speed = 6
check(adaptive.update_frame_step() == 0.1, "Speed switch resets adaptive step")
dispose(adaptive)
for selected_speed in range(1, 7):
var scheduled = fixture()
scheduled.speed = selected_speed
scheduled.evolute_frame(0.1, self)
check(not scheduled.frame_busy and scheduled.frame_commits == 0, "Wait for a full step at speed %d" % selected_speed)
scheduled.evolute_frame(0.1, self)
while scheduled.frame_busy: await process_frame
var cadence_clock: Dictionary = Clock.advance({"datetime": 0, "year": 0}, 0.2, selected_speed)
check(scheduled.frame_commits == 1 and E.eq(scheduled.state.time, cadence_clock), "One complete 0.2 second cycle at speed %d" % selected_speed)
scheduled.evolute_frame(0.0, self)
check(not scheduled.frame_busy and scheduled.frame_commits == 1, "No extra cycle without elapsed time")
dispose(scheduled)
var behind = fixture()
behind.evolute_frame(1.0, self)
while behind.frame_busy: await process_frame
check(E.eq(behind.state.time.datetime, 20000) and E.eq(behind.frame_clock.datetime, 100000), "Debt keeps the fixed step and preserves all requested time")
for cycle in 4:
behind.evolute_frame(0.0, self)
while behind.frame_busy: await process_frame
check(behind.frame_commits == 5 and E.eq(behind.state.time, behind.frame_clock), "One second is processed in exactly five full cycles")
dispose(behind)
print("Frame evolution: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)