14 Commits

Author SHA1 Message Date
Kirill a7c05e3796 Add report generation functionality to the UI
- Introduced a new script `report.gd` that extends `VBoxContainer` to manage the display of reports.
- Implemented dynamic localization support for report headers and body content based on the global locale.
- Created a `refresh` method to update the report content and header image when visibility changes or locale updates.
- Added a static `build` method to format and compile report data from the world state, including calculations for star totals and effects.
- Integrated presentation logic to render report templates based on the current state of the game world.
2026-10-18 21:48:15 +03:00
Kirill 2aced0c61a Add localization support for achievements: implement language-specific titles and update UI components 2026-10-18 20:32:20 +03:00
Kirill e1782c7b10 Implement achievement system: add achievement controller, UI components, and tests 2026-10-18 20:14:27 +03:00
Kirill e41d652631 Add achievement item UI and logic for displaying achievements 2026-10-18 20:03:27 +03:00
Kirill fe68e788b3 Add achievement loader script with unique identifier 2026-10-18 18:57:42 +03:00
Kirill 3047ef5a85 Implement apocalypse mechanics: add apocalypse queue, controller, and integrate into production logic 2026-10-18 00:05:53 +03:00
Kirill f53181b9ad Refactor production logic to incorporate helium share calculations and lifespan multipliers for stellar entities 2026-10-17 23:51:08 +03:00
Kirill 273093bd9e Разделить частоту обновления часов и расчёта симуляции
Счётчик времени UI обновляется до 10 Гц по запрошенному времени, а тяжёлые циклы по-прежнему запускаются с адаптивным интервалом и обрабатывают весь накопленный промежуток. Обновить тесты и описание модели.
2026-10-17 23:41:18 +03:00
Kirill 00b6192d19 Refactor production request handling: implement queuing mechanism and improve production application logic 2026-10-08 22:19:29 +03:00
Kirill 02c9843e1b Enhance civilization flow mechanics and adaptive quality control
- Implemented runtime-only adaptive quality controller to optimize simulation performance based on CPU time.
- Added adaptive quality thresholds and recovery cycles to dynamically adjust simulation fidelity.
- Introduced single-step solving method for civilization transitions to improve performance under high loads.
- Updated benchmark scripts to include speed parameter for testing various simulation speeds.
- Added tests for adaptive quality adjustments and their effects on civilization flow calculations.
- Documented results of optimization attempts and their impact on performance metrics.
2026-10-08 22:05:08 +03:00
Kirill 459fbb2a4d Refactor civilization flow reward calculations for improved accuracy and performance 2026-10-08 21:25:39 +03:00
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
Kirill 9357f757a9 Refactor civilization flow mechanics: enhance commit and colonization logic, add tests for flow application and resource allocation, and optimize performance metrics. 2026-10-08 20:43:51 +03:00
Kirill 413f2be904 Refactor simulation timing and frame processing logic for improved accuracy and performance 2026-10-08 00:15:23 +03:00
62 changed files with 5921 additions and 190 deletions
+5
View File
@@ -30,6 +30,9 @@ var Templates: Dictionary = {}
var LifeRules: Dictionary = {}
var CivilizationRules: Dictionary = {}
var ExtraParams: Dictionary = {}
var ApocalypseQueue: Dictionary = {}
var Achievements: Dictionary = {}
var Music: Dictionary = {}
var Effects: Dictionary = {}
@@ -92,6 +95,8 @@ func _ready() -> void:
OwniverseItems = OwniverseItemLoader.get_data()
Templates = OwniverseTemplateLoader.get_data()
CivilizationRules = OwniverseCivilizationRuleLoader.get_data()
ApocalypseQueue = OwniverseApocalypseQueueLoader.get_data()
Achievements = OwniverseAchievementLoader.get_data()
Music = OwniverseAudioLoader.get_music()
Effects = OwniverseAudioLoader.get_effects()
+9 -1
View File
@@ -250,8 +250,16 @@ static func _validate_payload(data: Variant, speed_limit: int, stages: Array, en
# Optional additions to the current format. Saves predating indicators keep
# their quantities and receive neutral defaults from Owniverse on load.
for field in saved:
if (not E.contains(required, field) and not E.contains(["world_parameters", "trends"], field)):
if (not E.contains(required, field) and not E.contains(["world_parameters", "trends", "apocalypse"], field)):
return "unsupported state structure"
if saved.has("apocalypse"):
var apocalypse: Variant = saved.apocalypse
if not apocalypse is Dictionary: return "invalid state.apocalypse"
for key in ["phase", "stage", "tick", "end_ticks"]:
if not _number(apocalypse.get(key), true): return E.add("invalid state.apocalypse.", key)
for key in ["type", "warning"]:
if not _number(apocalypse.get(key), true, false): return E.add("invalid state.apocalypse.", key)
if E.lt(apocalypse.phase, 0) or E.gt(apocalypse.phase, 2): return "invalid apocalypse phase"
if saved.has("world_parameters"):
if ((not _number_map(saved.world_parameters)) or E.ne(saved.world_parameters.size(), WorldParameters.defaults().size())):
return "invalid state.world_parameters"
+420
View File
@@ -0,0 +1,420 @@
class_name OwniverseAchievementLoader
static func get_data() -> Dictionary:
return {
'en': CSVLoader.load(EN),
'ru': CSVLoader.load(RU),
}
#region RU
const RU := """
id|Category|Points|progr: 60|Ignore|OpenCondition|AchieveCondition|Ru1|Ru2
a1|время|1|||age::1E9|age::1E9|Хорошее начало!|Вселенная существует миллиард лет.
a2|время|3|||age::1E10|age::1E10|Средний возраст.|Вселенная существует десять миллиардов лет.
a3|время|6|||age::1E11|age::1E11|Внушительный срок.|Вселенная существует сто миллиардов лет.
a4|время|10|||age::1E12|age::1E12|А не слишком ли это долго?|Вселенная существует триллион лет
a5|время|1||1||uptime|Вселенная под вашим управлением 13 минут подряд.|Неплохо для начала!
a6|время|3||1||uptime|Вселенная под вашим управлением 34 минуты подряд.|Без вас было бы хуже.
a7|время|6||1||uptime|Вселенная под вашим управлением 1 час 29 минут подряд.|Не привлекайте ненужного внимания.
a8|время|10||1||uptime|Вселенная под вашим управлением 3 часа 53 минуты подряд!|Можно и отдохнуть, это финальное достижение в данной линейке!
a9|время|1||1||played|Вы управляете вселенной 8 часов.|Её обитатели должны быть вам благодарны.
a10|время|3||1||played|Вы управляете вселенной 21 час.|Наверняка уже многое стало понятно.
a11|время|6||1||played|Вы управляете вселенной 2 суток и 7 часов.|Расскажите об игре друзьям!
a12|время|10||1||played|Вы управляете вселенной 6 суток.|Поделитесь опытом в социальных сетях!
a13|ресурсы|1||||total:H:1000|Получи всего 1 000 водорода|
a14|ресурсы|1|1||total:H:5E4|total:H:1E6|Получи всего 1 миллион водорода|
a15|ресурсы|1|1||total:H:5E7|total:H:1E9|Получи всего 1 миллиард водорода|
a16|ресурсы|3|1||total:H:5E10|total:H:1E12|Получи всего 1 триллион водорода|
a17|ресурсы|3|1||total:H:5E13|total:H:1E15|Получи всего 1 квадриллион водорода|
a18|ресурсы|3|1||total:H:5E16|total:H:1E18|Получи всего 1 квинтиллион водорода|
a19|ресурсы|6|1||total:H:5E19|total:H:1E21|Получи всего 1 секстиллион водорода|
a20|ресурсы|6|1||total:H:5E22|total:H:1E24|Получи всего 1 септиллион водорода|
a21|ресурсы|1||||total:S:1000|Создай 1 000 кубических парсек пространства|
a22|ресурсы|1|||total:S:5E4|total:S:1E6|Создай 1 миллион кубических парсек пространства|
a23|ресурсы|1|||total:S:5E7|total:S:1E9|Создай 1 миллиард кубических парсек пространства|
a24|ресурсы|3|||total:S:5E10|total:S:1E12|Создай 1 триллион кубических парсек пространства|
a25|ресурсы|3|||total:S:5E13|total:S:1E15|Создай 1 квадриллион кубических парсек пространства|
a26|ресурсы|3|||total:S:5E16|total:S:1E18|Создай 1 квинтиллион кубических парсек пространства|
a27|ресурсы|6|||total:S:5E19|total:S:1E21|Создай 1 секстиллион кубических парсек пространства|
a28|ресурсы|6|||total:S:5E22|total:S:1E24|Создай 1 септиллион кубических парсек пространства|
a29|ресурсы|1|||total:He:1E9|total:He:1E9|Получи 1 миллиард гелия|
a30|ресурсы|3|||total:He:1E15|total:He:1E15|Получи 1 квадриллион гелия|
a31|ресурсы|6|||total:He:1E21|total:He:1E21|Получи 1 секстиллион гелия|
a32|ресурсы|1||1|||Израсходуй 1 миллион гелия|
a33|ресурсы|3||1|||Израсходуй 1 триллион гелия|
a34|ресурсы|6||1|||Израсходуй 1 квинтиллион гелия|
a35|ресурсы|1||1|total:Voids:5|total:Voids:25|Сформируй 25 войдов|
a36|ресурсы|1||1|total:Voids:25|total:Voids:125|Сформируй 125 войдов|
a37|ресурсы|1||1|total:Voids:125|total:Voids:625|Сформируй 625 войдов|
a38|ресурсы|3||1|total:Voids:625|total:Voids:3125|Сформируй 3125 войдов|
a39|ресурсы|3||1|total:Voids:3125|total:Voids:15625|Сформируй 15625 войдов|
a40|ресурсы|6||1|total:Voids:15625|total:Voids:78125|Сформируй 78125 войдов|Продолжай в том же духе, без войдов вселенная обречена!
a41|ресурсы|10||1|energy::1E27|energy::1E27|На складе пусто!|Потрачен весь запас энергии.
a42|объекты|1||||total:Star:1|Зажги звезду|
a43|объекты|1|||total:OD:1|total:OD:1|Зажги оранжевого карлика|
a44|объекты|1||||total:YD:1|Зажги жёлтого карлика|
a45|объекты|1|||total:WYD:1|total:WYD:1|Зажги бело-жёлтого карлика|
a46|объекты|1|||total:WS:1|total:WS:1|Зажги белую звезду|
a47|объекты|1||||total:BG:1|Зажги голубого гиганта|
a48|объекты|1|1||total:BG:1|total:SG:1|Зажги сверхгиганта|
a49|объекты|1|1||total:SG:1|total:HG:1|Зажги гипергиганта|
a50|объекты|1||||total:WD:1|Получи белого карлика|
a51|объекты|1||||total:NS:1|Получи нейтронную звезду|
a52|объекты|1|1||total:NS:1|total:MGT:1|Получи магнетар|
a53|объекты|1||||total:BH:1|Получи чёрную дыру|
a54|объекты|1|1||total:BH:1|total:SMBH:1|Создай сверхмассивную чёрную дыру|
a55|объекты|1||||total:N:1|Создай туманность|
a56|объекты|1|1||total:N:1|total:G:1|Создай глобулу|
a57|объекты|1|1||total:G:1|total:STN:1|Создай звёздную колыбель|
a58|объекты|1|1||total:G:1|total:MC:1|Создай молекулярное облако|
a59|объекты|3|1||total:STN:1|total:ASTN:1|Создай активную звёздную колыбель|
a60|объекты|3|1||total:MC:100|total:MC:1E4|Создай 10 000 молекулярных облаков|
a61|объекты|1|1|1|auto:Star:1|auto:Star:10|Создай 10 звёзд с помощью облаков|
a62|объекты|1|1|1|auto:Star:10|auto:Star:1000|Создай 1 000 звёзд с помощью облаков|
a63|объекты|3|1|1|auto:Star:1000|auto:Star:1E6|Создай 1 миллион звёзд с помощью облаков|
a64|объекты|1|1||total:Star:30|total:Star:300|Зажги 300 звёзд|
a65|объекты|1|1||total:OD:1|total:OD:2000|Зажги 2 000 оранжевых карликов|
a66|объекты|1|1||total:WYD:1|total:WYD:500|Зажги 500 бело-жёлтых карликов|
a67|объекты|1|1||total:BD:1|total:BD:1E6|Зажги 1 миллион бурых карликов|
a68|объекты|3|||total:HG:1000|total:HG:1000|Это кому-нибудь нужно?|Зажги 1 000 гипергигантов.
a69|объекты|3|1||total:Star:5000|total:Star:1E7|Зажги 10 миллионов звёзд|
a70|объекты|3|||total:Star:4E9|total:Star:4E11|Зажги 400 миллиардов звёзд|
a71|объекты|3|||total:Star:9E14|total:Star:9E16|Зажги 90 квадриллионов звёзд|
a72|объекты|3|1||total:SMBH:1|total:SMBH:1E4|Сбрось 10 000 вещества в сверхмассивные чёрные дыры|
a73|объекты|3|||total:SMBH:3E5|total:SMBH:4.2E7|Сбрось 42 миллиона вещества в сверхмассивные чёрные дыры|Примерно столько весит Стрелец А со звёздочкой.
a74|объекты|3|||total:SMBH:3E9|total:SMBH:6.6E11|Сбрось 660 миллиардов вещества в сверхмассивные чёрные дыры|Примерно столько весит TON 618.
a75|структуры|1||||total:SC:1|Создай любое звёздное скопление|
a76|структуры|1||||total:SCM:1|Создай среднее звёздное скопление|
a77|структуры|1|1||total:SC:1|total:SCL:1|Создай большое звёздное скопление|
a78|структуры|3|1||total:SCL:1|total:SCG:1|Создай гигантское звёздное скопление|
a79|структуры|3|1||total:SC:10|total:SC:100|Создай 100 звёздных скоплений|
a80|структуры|1||||total:GAL:1|Создай любую галактику|
a81|структуры|3|1|1|total:GAL:1|total:GALS:1|Создай спиральную галактику|
a82|структуры|1||||total:GEM:1|Создай среднюю галактику|
a83|структуры|1|1||total:GAL:1|total:GEL:1|Создай большую галактику|
a84|структуры|3|1||total:GEL:1|total:GEG:1|Создай гигантскую галактику|
a85|структуры|3|1||total:GAL:5|total:GAL:30|Создай 30 галактик|
a86|структуры|1|1||total:GAL:30|total:LGG:1|Создай группу галактик|
a87|структуры|3|1||total:LGG:1|total:LGG:30|Создай 30 групп галактик|
a88|структуры|3|1||total:LGG:30|total:LSC:1|Создай галактическое сверхскопление|
a89|структуры|3|1||total:LSC:1|total:LSC:30|Создай 30 галактических сверхскоплений|
a90|структуры|6|1||total:LSC:30|total:LGF:1|Создай галактическую нить|
a91|структуры|6|1||total:LGF:1|total:LGF:100|Создай 100 галактических нитей|
a92|структуры|6|1||total:LGF:100|total:LGW:1|Создай великую стену|
a93|структуры|6|1||total:LGW:1|total:LGW:100|Создай 100 великих стен|
a94|структуры|6|1||total:LGW:100|total:LUB:1|Создай вселенский пузырь|
a95|структуры|10|1|1|total:LUB:1|total:LUB:1|Это должно было когда-то закончиться!|Создана крупномасштабная структура вселенной.
a96|жизнь|1||1||Planet:1|Создай любую планету|
a97|жизнь|1|1|1|GG:1^AB:1|RP:1|Создай каменистую планету|
a98|жизнь|1|1|1|RP:1|HP:1|Создай обитаемую планету|
a99|жизнь|3||1||EON1:1|Создай жизнь|
a100|жизнь|1|1|1|EON1:1|EON2:1|Вырасти амёб и водоросли|
a101|жизнь|1|1|1|EON2:1|EON3:1|Вырасти трилобитов и рыб|
a102|жизнь|1|1|1|EON3:1|EON4:1|Вырасти динозавров и птиц|
a103|жизнь|1|1|1|EON4:1|EON5:1|Вырасти млекопитающих и приготовься...|
a104|жизнь|1|1|1|Planet:1|Planet:1E5|Создай 100 000 планет|
a105|жизнь|1|1|1|HP:1|HP:1E4|Создай 10 000 обитаемых планет|
a106|жизнь|3|1|1|EON1:1|EON1:1000|Создай жизнь на 1 000 планет|
a107|жизнь|6||1|C0.0:1|C0.0:1|Возник разум!|
a108|жизнь|1|1|1|C0.0:1|C0.5:1|Они умеют лепить глиняные горшки!|Цивилизация достигла уровня 0.5.
a109|жизнь|3|1|1|C0.7:1|C1.0:1|Они освоили свою родную планету!|Цивилизация достигла уровня 1.0.
a110|жизнь|1|1|1|C1.3:1|C1.5:1|Они построили космический город!|Цивилизация достигла уровня 1.5.
a111|жизнь|6|1|1|C1.7:1|C2.0:1|У них есть рой Дайсона!|Цивилизация достигла уровня 2.0.
a112|жизнь|1||1|C2.2:1|C2.2:1|Вроде бы они начали расти!|Цивилизация заселила сотню звёзд.
a113|жизнь|1||1|C2.5:1|C2.5:1|Их почти можно увидеть невооружённым глазом!|Цивилизация заселила среднее звёздное скопление.
a114|жизнь|3||1|C2.8:1|C2.8:1|О, они наконец-то поняли, как управлять гравитацией!|Цивилизация достигла уровня 2.8.
a115|жизнь|6||1|C3.0:1|C3.0:1|Они и правда научились летать!|Цивилизация способна путешествовать между галактиками.
a116|жизнь|3||1|C3.2:1|C3.2:1|Они наверное так гордятся собой!|Цивилизация расселилась на масштабах порядка Туманности Андромеды.
a117|жизнь|3||1|C3.5:1|C3.5:1|Вот это они смекалистые!|Цивилизация использует ресурсы нейтронной звезды.
a118|жизнь|3||1|C3.8:1|C3.8:1|Теперь их и правда легко заметить.|Цивилизация заселила сотни тысяч галактик.
a119|жизнь|6||1|C4.0:1|C4.0:1|Кажется это уже было, нет?|Цивилизация освоила манипуляции со временем.
a120|жизнь|6||1|C4.2:1|C4.2:1|Наконец-то они перестали мелочиться.|Цивилизация использует сверхмассивную чёрную дыру для своих целей.
a121|жизнь|10||1|C4.4:1|C4.4:1|Они справились! Интересно, чем же дальше займутся?|Цивилизация расселилась на масштабах порядка всей видимой вселенной.
a122|жизнь|3|1|1|C1.6:1|SET:1|Они основали колонию у соседней звезды!|
a123|жизнь|6|1|1||GSM:1&C2.8:50|Они похоже основали Галактическую Федерацию или что-то в этом роде!|
a124|жизнь|1||1|||О нет...|Погибла цивилизация 0.8+ уровня.
a125|жизнь|1||1|||Да сколько можно!|Погибло 100 цивилизаций 1.2+ уровня.
a126|жизнь|1||1|||К этому вообще можно привыкнуть?|Погибло 1 000 цивилизаций 1.6+ уровня.
a127|жизнь|3||1|||Да, в какой-то момент становится безразлично.|Погибло 10 000 цивилизаций 2.0+ уровня
a128|жизнь|6||1|||А выглядели крепкими. Что ж поделать.|Погибла цивилизация 3.0+ уровня.
a129|жизнь|10||1|||Моё разочарование неизмеримо.|Погибла цивилизация 4.0+ уровня.
a130|жизнь|6||1|||Что-то это мне напоминает...|На момент 13.8 миллиардов лет существует цивилизация уровня 0.9.
a131|события|3|1|1|высокая плотность|BigCrunch|Большое сжатие!|Вселенная схлопнулась в колоссальную чёрную дыру.
a132|события|3|1|1|низкая плотность|BigRip|Большой разрыв!|Вселенная рассеялась в разные стороны.
a133|события|6|1|1|BigCrunch^BigRip|BigCrunch&BigRip|Двойная неурядица!|Два разных апокалипсиса завершены.
a134|события|10||1|||Изгнание! Вас выгнали из вселенной.|Теперь эта вселенная существует сама по себе, без вашего участия.
a135|события|3||1|||Антитеизм: они отрицают ваше существование.|Может они в чём-то и правы?
a136|события|3||1|||Интеграция: они стремятся к сосуществованию.|Может быть хотя бы у них получится?
a137|события|3||1|||Ксеноцид: их воинственность приводит к ужасным результатам.|Впрочем, ничего нового.
a138|события|3||1|||Реконструкция: они переделывают вселенную.|Это же их вселенная, да? Или нет?
a139|события|3||1|||Духовность: они ищут глубинный смысл.|Что если они узнают правду?
a140|события|3||1|||Исход: они хотят покинуть вселенную.|Что же им здесь не нравится?
a141|события|6||1|||Динамичная вселенная|Все тенденции проявили себя!
a142|события|3||1|||Изгнание: они пытаются выгнать из вселенной вас!|Их определённо что-то не устраивает.
a143|события|3||1|||Федерация: они ищут способы мирного сосуществования.|Наверное это неплохо?
a144|события|3||1|||Просвещение: они помогают отстающим.|Какая прелесть!
a145|события|3||1|||Новый порядок: они закапывают топор войны.|Может оно и к лучшему?
a146|события|3||1|||Великие войны: они устроили эпическое побоище.|Жаль, что отсюда не рассмотреть всех деталей.
a147|события|3||1|||Истребление: они занимаются чисткой потенциальных конкурентов.|Жизнь — это возобновляемый ресурс.
a148|события|3||1|||Опустошение: они разрушают вселенную!|Звёзды превращаются в чёрные дыры.
a149|события|3||1|||Звёздное дробление: они перестраивают вселенную под себя.|Им удобнее жить на малых и стабильных звёздах.
a150|события|3||1|||Звёздная накачка: они создают новые звёзды!|Из облаков и бурых карликов создаются красные карлики.
a151|события|3||1|||Звёздное охлаждение: они стабилизируют звёзды.|Снижается активность звёздных процессов.
a152|события|3||1|||Молитва: они невероятно истово молятся.|Вы даже смогли что-то расслышать.
a153|события|3||1|||Диалог: они ждут ответа!|А отвечать именно вам.
a154|события|3||1|||Живая вселенная|Произошло 10 действий цивилизаций.
a155|события|6||1|||Очень живая вселенная|Произошло 30 действий цивилизаций.
a156|события|10||1|||Невероятно живая вселенная!|Произошло 100 действий цивилизаций.
a157|события|10||1|||Столько всего происходит!|Все действия произошли хотя бы один раз!
a158|силы|1||1|сила получена|сила применена|Научись быстро копать.|Используй лопатин
a159|силы|1||1|сила получена|сила применена|Запусти инфляцию.|Используй надувин
a160|силы|3||1|сила получена|сила применена|Пропусти время.|Используй временит
a161|силы|3||1|сила получена|сила применена|Утилизируй мусор.|Используй столкновит
a162|силы|6||1|сила получена|сила применена|Подели физику на ноль.|Используй первекс
a163|силы|1||1|сила получена|сила применена|Уничтожь пространство.|Используй сингулярит
a164|силы|1||1|сила получена|сила применена|Укрепи структуры.|Используй обновин
a165|силы|3||1|сила получена|сила применена|Разожги звёзды.|Используй вливанит
a166|силы|3||1|сила получена|сила применена|Остуди вселенную.|Используй морозин
a167|силы|6||1|сила получена|сила применена|Избавься от звёзд.|Используй дезвездит
a168|силы|1||1|сила получена|сила применена|Сделай больше планет.|Используй камнекс
a169|силы|1||1|сила получена|сила применена|Осемени вселенную.|Используй панспермин
a170|силы|3||1|сила получена|сила применена|Удобри вселенную.|Используй гайанит
a171|силы|3||1|сила получена|сила применена|Взбодри вселенную.|Используй дарвинит
a172|силы|6||1|сила получена|сила применена|Наполни вселенную благостью.|Используй эдемекс
a173|силы|1||1|сила получена|сила применена|Научи разумных жить в мире.|Используй пацифин
a174|силы|1||1|сила получена|сила применена|Научи разумных сражаться за свою жизнь.|Используй озверин
a175|силы|3||1|сила получена|сила применена|Назначь пророков среди разумных.|Используй моралит
a176|силы|3||1|сила получена|сила применена|Покарай тех разумных, кто не следует твоим планам.|Используй дискриминит
a177|силы|6||1|сила получена|сила применена|Подави тех разумных, которые слишком много о себе возомнили.|Используй репрекс
a178|силы|6||1|одна любая сила получена в 4 градациях|флэш|Потрать силу одного типа по нарастанию градаций.|Зелёная, синяя, фиолетовая, оранжевая
a179|силы|3||1|снижена вручную|Bur=1|Холодная вселенная.|Минимальная горючесть
a180|силы|3||1|аналогично|Bur=19|Звёзды зажигают, чтобы они сгорели!|Максимальная горючесть
a181|силы|3||1|аналогично|Exp=1|Плотный вакуум.|Минимальная взрывчатость
a182|силы|3||1|аналогично|Exp=19|Бада-бим, бада-бум-бум-бум!|Максимальная взрывчатость
a183|силы|3||1|аналогично|Agg=1|Мир и покой.|Минимальная агрессия
a184|силы|3||1|аналогично|Agg=19|Как 4Е4!|Максимальная агрессия
a185|силы|6||1|||Все силы доступны одновременно!|
a186|силы|10||1|||Все силы использованы хотя бы по одному разу в этой вселенной.|
a187|силы|1||1|3 обычные силы одного вида||Улучшите силу до редкой.|
a188|силы|3||1|3 редкие силы одного вида||Улучшите силу до эпической.|
a189|силы|6||1|3 эпические силы одного вида||Улучшите силу до легендарной.|
a190|силы|1||1|улучш 3 силы|5|Улучшите 5 сил.|
a191|силы|1||1|8|13|Улучшите 13 сил.|
a192|силы|3||1|21|34|Улучшите 34 силы.|
a193|силы|3||1|55|89|Улучшите 89 сил.|
a194|силы|6||1|144|233|Улучшите 233 силы.|
a195|силы|6||1|377|610|Улучшите 610 сил.|
a196|силы|10||1|987|1597|Улучшите 1 597 сил.|Продолжайте в том же духе!
a197|силы|1||1|1|3|Улучшите 3 сундука с силами.|
a198|силы|3||1|5|7|Улучшите 7 сундуков с силами.|
a199|силы|6||1|14|20|Улучшите 20 сундуков с силами.|
a200|силы|10||1|38|55|Улучшите 55 сундуков с силами.|Спасибо за поддержку!
"""
#endregion
#region EN
const EN := """
id|Category|Points|progr: 60|Ignore|OpenCondition|AchieveCondition|En1|En2
a1|время|1|||age::1E9|age::1E9|A good start!|The Universe exists for a billion years.
a2|время|3|||age::1E10|age::1E10|Middle age.|The Universe exists for a ten billions years.
a3|время|6|||age::1E11|age::1E11|Impressive term.|The Universe exists for a hundred billions years.
a4|время|10|||age::1E12|age::1E12|Isn't it too long?|The Universe exists for a trillion years.
a5|время|1||1||uptime|The universe is under your control for 13 minutes in a row.|Not bad for a start!
a6|время|3||1||uptime|The universe is under your control for 34 minutes in a row.|It would be worse without you.
a7|время|6||1||uptime|The universe is under your control for 1 hour 29 minutes in a row.|Don't draw unnecessary attention.
a8|время|10||1||uptime|The universe is under your control for 3 hours 53 minutes in a row!|You can also relax, this is the final achievement in this line!
a9|время|1||1||played|You rule the universe for 8 hours.|Its inhabitants should be grateful to you.
a10|время|3||1||played|You rule the universe for 21 hours.|Surely a lot has become clear by now.
a11|время|6||1||played|You rule the universe for 2 days and 7 hours.|Tell your friends about the game!
a12|время|10||1||played|You rule the universe for 6 days.|Share your experience on social networks!
a13|ресурсы|1||||total:H:1000|Get total 1 000 hydrogen|
a14|ресурсы|1|1||total:H:5E4|total:H:1E6|Get total 1 million hydrogen|
a15|ресурсы|1|1||total:H:5E7|total:H:1E9|Get total 1 billion hydrogen|
a16|ресурсы|3|1||total:H:5E10|total:H:1E12|Get total 1 trillion hydrogen|
a17|ресурсы|3|1||total:H:5E13|total:H:1E15|Get total 1 quadrillion hydrogen|
a18|ресурсы|3|1||total:H:5E16|total:H:1E18|Get total 1 quintillion hydrogen|
a19|ресурсы|6|1||total:H:5E19|total:H:1E21|Get total 1 sextillion hydrogen|
a20|ресурсы|6|1||total:H:5E22|total:H:1E24|Get total 1 septillion hydrogen|
a21|ресурсы|1||||total:S:1000|Create 1 000 cubic parsecs of space|
a22|ресурсы|1|||total:S:5E4|total:S:1E6|Create 1 million cubic parsecs of space|
a23|ресурсы|1|||total:S:5E7|total:S:1E9|Create 1 billion cubic parsecs of space|
a24|ресурсы|3|||total:S:5E10|total:S:1E12|Create 1 trillion cubic parsecs of space|
a25|ресурсы|3|||total:S:5E13|total:S:1E15|Create 1 quadrillion cubic parsecs of space|
a26|ресурсы|3|||total:S:5E16|total:S:1E18|Create 1 quintillion cubic parsecs of space|
a27|ресурсы|6|||total:S:5E19|total:S:1E21|Create 1 sextillion cubic parsecs of space|
a28|ресурсы|6|||total:S:5E22|total:S:1E24|Create 1 septillion cubic parsecs of space|
a29|ресурсы|1|||total:He:1E9|total:He:1E9|Obtain 1 billion helium|
a30|ресурсы|3|||total:He:1E15|total:He:1E15|Obtain 1 quadrillion helium|
a31|ресурсы|6|||total:He:1E21|total:He:1E21|Obtain 1 sextilion helium|
a32|ресурсы|1||1|||Use up 1 million helium|
a33|ресурсы|3||1|||Use up 1 trillion helium|
a34|ресурсы|6||1|||Use up 1 quintillion helium|
a35|ресурсы|1||1|total:Voids:5|total:Voids:25|Form 25 voids|
a36|ресурсы|1||1|total:Voids:25|total:Voids:125|Form 125 voids|
a37|ресурсы|1||1|total:Voids:125|total:Voids:625|Form 625 voids|
a38|ресурсы|3||1|total:Voids:625|total:Voids:3125|Form 3125 voids|
a39|ресурсы|3||1|total:Voids:3125|total:Voids:15625|Form 15625 voids|
a40|ресурсы|6||1|total:Voids:15625|total:Voids:78125|Form 78125 voids|Keep up the good work, without voids the universe is doomed!
a41|ресурсы|10||1|energy::1E27|energy::1E27|Out of ammo!|All energy cache is spent.
a42|объекты|1||||total:Star:1|Make a Star|
a43|объекты|1|||total:OD:1|total:OD:1|Make an Orange Dwarf|
a44|объекты|1||||total:YD:1|Make a Yellow Dwarf|
a45|объекты|1|||total:WYD:1|total:WYD:1|Make a White-Yellow Dwarf|
a46|объекты|1|||total:WS:1|total:WS:1|Make a White Star|
a47|объекты|1||||total:BG:1|Make a Blue Giant|
a48|объекты|1|1||total:BG:1|total:SG:1|Make a Supergiant|
a49|объекты|1|1||total:SG:1|total:HG:1|Make a Hypergiant|
a50|объекты|1||||total:WD:1|Get a White Dwarf|
a51|объекты|1||||total:NS:1|Get a Neutron Star|
a52|объекты|1|1||total:NS:1|total:MGT:1|Get a Magnetar|
a53|объекты|1||||total:BH:1|Get a Black Hole|
a54|объекты|1|1||total:BH:1|total:SMBH:1|Make a Supermassive Black Hole|
a55|объекты|1||||total:N:1|Make a Nebula|
a56|объекты|1|1||total:N:1|total:G:1|Make a Globula|
a57|объекты|1|1||total:G:1|total:STN:1|Make a Star Nursery|
a58|объекты|1|1||total:G:1|total:MC:1|Make a Molecular Cloud|
a59|объекты|3|1||total:STN:1|total:ASTN:1|Make an Active Star Nursery|
a60|объекты|3|1||total:MC:100|total:MC:1E4|Make 10 000 Molecular Clouds|
a61|объекты|1|1|1|auto:Star:1|auto:Star:10|Make 10 stars via clouds|
a62|объекты|1|1|1|auto:Star:10|auto:Star:1000|Make 1 000 stars via clouds|
a63|объекты|3|1|1|auto:Star:1000|auto:Star:1E6|Make 1 million stars via clouds|
a64|объекты|1|1||total:Star:30|total:Star:300|Make 300 stars|
a65|объекты|1|1||total:OD:1|total:OD:2000|Make 2 000 orange dwarfs|
a66|объекты|1|1||total:WYD:1|total:WYD:500|Make 500 white-yellow dwarfs|
a67|объекты|1|1||total:BD:1|total:BD:1E6|Make 1 million brown dwarfs|
a68|объекты|3|||total:HG:1000|total:HG:1000|Someone must need them, of course?|Make 1 000 hypergiants.
a69|объекты|3|1||total:Star:5000|total:Star:1E7|Make 10 million stars|
a70|объекты|3|||total:Star:4E9|total:Star:4E11|Make 400 billion stars|
a71|объекты|3|||total:Star:9E14|total:Star:9E16|Make 90 quadrillion stars|
a72|объекты|3|1||total:SMBH:1|total:SMBH:1E4|Drop 10 000 matter into Supermassive Black Holes|
a73|объекты|3|||total:SMBH:3E5|total:SMBH:4.2E7|Drop 42 million matter into Supermassive Black Holes|Sagittarius A* weighs about as much.
a74|объекты|3|||total:SMBH:3E9|total:SMBH:6.6E11|Drop 660 billion matter into Supermassive Black Holes|TON 618 weighs about as much.
a75|структуры|1||||total:SC:1|Make a Star Cluster|
a76|структуры|1||||total:SCM:1|Make a Medium Star Cluster|
a77|структуры|1|1||total:SC:1|total:SCL:1|Make a Large Star Cluster|
a78|структуры|3|1||total:SCL:1|total:SCG:1|Make a Giant Star Cluster|
a79|структуры|3|1||total:SC:10|total:SC:100|Make 100 Star Clusters|
a80|структуры|1||||total:GAL:1|Make a Galaxy|
a81|структуры|3|1|1|total:GAL:1|total:GALS:1|Make a Spiral Galaxy|
a82|структуры|1||||total:GEM:1|Make a Medium Galaxy|
a83|структуры|1|1||total:GAL:1|total:GEL:1|Make a Large Galaxy|
a84|структуры|3|1||total:GEL:1|total:GEG:1|Make a Giant Galaxy|
a85|структуры|3|1||total:GAL:5|total:GAL:30|Make 30 Galaxies|
a86|структуры|1|1||total:GAL:30|total:LGG:1|Make a Galactic Group|
a87|структуры|3|1||total:LGG:1|total:LGG:30|Make 30 Galactic Groups|
a88|структуры|3|1||total:LGG:30|total:LSC:1|Make a Galactic Supercluster|
a89|структуры|3|1||total:LSC:1|total:LSC:30|Make 30 Galactic Superclusters|
a90|структуры|6|1||total:LSC:30|total:LGF:1|Make a Galactic Filament|
a91|структуры|6|1||total:LGF:1|total:LGF:100|Make 100 Galactic Filaments|
a92|структуры|6|1||total:LGF:100|total:LGW:1|Make a Great Wall|
a93|структуры|6|1||total:LGW:1|total:LGW:100|Make 100 Great Walls|
a94|структуры|6|1||total:LGW:100|total:LUB:1|Make an Universe Bubble|
a95|структуры|10|1|1|total:LUB:1|total:LUB:1|It had to end sometime!|The large-scale structure of the universe has been created.
a96|жизнь|1||1||Planet:1|Make a planet|
a97|жизнь|1|1|1|GG:1^AB:1|RP:1|Make a rocky planet|
a98|жизнь|1|1|1|RP:1|HP:1|Make a habitable planet|
a99|жизнь|3||1||EON1:1|Make life|
a100|жизнь|1|1|1|EON1:1|EON2:1|Grow amoebas and algae|
a101|жизнь|1|1|1|EON2:1|EON3:1|Grow trilobites and fishes|
a102|жизнь|1|1|1|EON3:1|EON4:1|Grow dinosaurs and birds|
a103|жизнь|1|1|1|EON4:1|EON5:1|Grow mammals and prepare yourself...|
a104|жизнь|1|1|1|Planet:1|Planet:1E5|Make 100 000 planets|
a105|жизнь|1|1|1|HP:1|HP:1E4|Make 10 000 habitable planets|
a106|жизнь|3|1|1|EON1:1|EON1:1000|Make life at 1 000 planets|
a107|жизнь|6||1|C0.0:1|C0.0:1|Sapience emerged!|
a108|жизнь|1|1|1|C0.0:1|C0.5:1|They can make pots out of clay!|A civilization has reached 0.5 level.
a109|жизнь|3|1|1|C0.7:1|C1.0:1|They have mastered their home planet!|A civilization has reached 1.0 level.
a110|жизнь|1|1|1|C1.3:1|C1.5:1|They have built a space city!|A civilization has reached 1.5 level.
a111|жизнь|6|1|1|C1.7:1|C2.0:1|They have a Dyson Swarm!|A civilization has reached 2.0 level.
a112|жизнь|1||1|C2.2:1|C2.2:1|They seem to grow at last!|A civilization has settled over a hundred of stars.
a113|жизнь|1||1|C2.5:1|C2.5:1|They are almost noticeable by a naked eye!|A civilization has settled over a Medium Star Cluster.
a114|жизнь|3||1|C2.8:1|C2.8:1|Oh, they grasped how to control gravity at last!|A civilization has reached 2.8 level.
a115|жизнь|6||1|C3.0:1|C3.0:1|They really have learned to fly!|A civilization is able to travel between Galaxies.
a116|жизнь|3||1|C3.2:1|C3.2:1|They must be so proud of themselves!|A civilization has settled on a scale of an Andromeda Galaxy.
a117|жизнь|3||1|C3.5:1|C3.5:1|Hey, they are resourceful!|A civilization has utilized a Neutron Star resources.
a118|жизнь|3||1|C3.8:1|C3.8:1|Now they actually easy to notice.|A civilization has settled over hundreds of thousands Galaxies.
a119|жизнь|6||1|C4.0:1|C4.0:1|Feels like this has happened already, no?|A civilization has grasp on temporal manipulations.
a120|жизнь|6||1|C4.2:1|C4.2:1|At last they seem to think big enough.|A civilization has utilized a Supermassive Black Hole for their goals.
a121|жизнь|10||1|C4.4:1|C4.4:1|They've made it! What will they do next, I wonder?|A civilization has expanded on a scale of a whole visible Universe.
a122|жизнь|3|1|1|C1.6:1|SET:1|They have made an extrasolar colony!|
a123|жизнь|6|1|1||GSM:1&C2.8:50|They seem to have found a Galactic Federation or something!|
a124|жизнь|1||1|||Oh no...|A civilization of 0.8+ level extinct.
a125|жизнь|1||1|||Not again!|100 civilizations of 1.2+ level extinct.
a126|жизнь|1||1|||Will I get used to it?|1 000 civilizations of 1.6+ level extinct.
a127|жизнь|3||1|||Yeah, you become numb after a while.|10 000 civilizations of 2.0+ level extinct.
a128|жизнь|6||1|||They seemed to be survivors. Oh well.|A civilization of 3.0+ level extinct.
a129|жизнь|10||1|||My dissapointment is immeasureable.|A civilization of 4.0+ level extinct.
a130|жизнь|6||1|||This reminds me of something...|At the date of 13.8 billions of years there is a civilization of 0.9 level.
a131|события|3|1|1|высокая плотность|BigCrunch|Big Crunch!|The universe collapsed into a colossal black hole.
a132|события|3|1|1|низкая плотность|BigRip|Big Rip!|The universe dissipated in different directions.
a133|события|6|1|1|BigCrunch^BigRip|BigCrunch&BigRip|Double Trouble!|Two different apocalypses are finished.
a134|события|10||1|||Complete banishment! You have been kicked out of the universe.|Now this universe exists by itself, without your participation.
a135|события|3||1|||Antitheism: They deny your existence.|Maybe they are somehow right about that?
a136|события|3||1|||Integration: They strive for coexistence.|Maybe at least they can make it?
a137|события|3||1|||Xenocide: Their bellicosity leads to dire results.|However, nothing is new.
a138|события|3||1|||Reconstruction: They rearrange the universe.|It is their universe, right? Or not?
a139|события|3||1|||Spirituality: They seek deep meaning.|What if they find out the truth?
a140|события|3||1|||Exodus: They want to leave the universe.|What do they not like here?
a141|события|6||1|||Dynamic Universe|All trends have shown themselves!
a142|события|3||1|||Banishment: They are trying to expel you from the universe!|They definitely don't like something.
a143|события|3||1|||Federation: They are looking for ways to peacefully coexist.|Perhaps this is good?
a144|события|3||1|||Enlightenment: They help those who are lagging behind.|How lovely!
a145|события|3||1|||New Order: They bury the hatchet.|Maybe it's for the best?
a146|события|3||1|||Grand Wars: They are engaged into carnage of epic proportions.|It is a pity that it is impossible to discern all the details from here.
a147|события|3||1|||Extermination: They are purging potential competitors.|Life is a renewable resource.
a148|события|3||1|||Devastation: They are destroying the universe!|Stars turn into black holes.
a149|события|3||1|||Stellar Fragmentation: They reconstruct the universe for themselves.|It is more convenient for them to live on small and stable stars.
a150|события|3||1|||Stellar Pumping: They create new stars!|Red dwarfs are created from clouds and brown dwarfs.
a151|события|3||1|||Stellar Cooling: They stabilize the stars.|The activity of stellar processes is decreasing.
a152|события|3||1|||Prayer: They pray incredibly fervently.|You were even able to hear something.
a153|события|3||1|||Dialogue: They are waiting for an answer!|And it is you who should answer.
a154|события|3||1|||Living Universe|10 civilization actions have taken place.
a155|события|6||1|||Very Living Universe|30 civilization actions have taken place.
a156|события|10||1|||Incredibly Alive Universe!|100 civilization actions have taken place.
a157|события|10||1|||So much is happening!|All actions happened at least once!
a158|силы|1||1|сила получена|сила применена|Learn to dig fast.|Use Shovelin
a159|силы|1||1|сила получена|сила применена|Kickstart inflation.|Use Inflatin
a160|силы|3||1|сила получена|сила применена|Pass time.|Use Temporite
a161|силы|3||1|сила получена|сила применена|Dispose of garbage.|Use Collisite
a162|силы|6||1|сила получена|сила применена|Divide physics by zero.|Use Primex
a163|силы|1||1|сила получена|сила применена|Destroy space.|Use Singularite
a164|силы|1||1|сила получена|сила применена|Strengthen structures.|Use Renewin
a165|силы|3||1|сила получена|сила применена|Light up the stars.|Use Infusite
a166|силы|3||1|сила получена|сила применена|Cool down the universe.|Use Frostin
a167|силы|6||1|сила получена|сила применена|Get rid of the stars.|Use Destellarite
a168|силы|1||1|сила получена|сила применена|Make more planets.|Use Rockex
a169|силы|1||1|сила получена|сила применена|Seed the universe.|Use Panspermin
a170|силы|3||1|сила получена|сила применена|Fertilize the universe.|Use Gaianite
a171|силы|3||1|сила получена|сила применена|Invigorate the universe.|Use Darwinite
a172|силы|6||1|сила получена|сила применена|Fill the universe with goodness.|Use Edenex
a173|силы|1||1|сила получена|сила применена|Teach sapients to live in peace.|Use Pacifin
a174|силы|1||1|сила получена|сила применена|Teach sapients to fight for their lives.|Use Enragein
a175|силы|3||1|сила получена|сила применена|Appoint prophets among sapients.|Use Moralite
a176|силы|3||1|сила получена|сила применена|Punish those sapients who do not follow your plans.|Use Discriminite
a177|силы|6||1|сила получена|сила применена|Suppress those sapients who think too much of themselves.|Use Reprex
a178|силы|6||1|одна любая сила получена в 4 градациях|флэш|Use the forces of the one kind by increase of power.|Green, blue, purple, orange
a179|силы|3||1|снижена вручную|Bur=1|Cold universe.|Minimal Burnability
a180|силы|3||1|аналогично|Bur=19|The stars are lit to burn out!|Maximum Burnability
a181|силы|3||1|аналогично|Exp=1|Dense vacuum.|Minimal Explosiveness
a182|силы|3||1|аналогично|Exp=19|Bada-bing, bada-boom-boom-boom!|Maximum Explosiveness
a183|силы|3||1|аналогично|Agg=1|Peace and tranquility.|Minimal Aggression
a184|силы|3||1|аналогично|Agg=19|Like 4E4!|Maximum Aggression
a185|силы|6||1|||All forces are available at once!|
a186|силы|10||1|||All forces are used at least once in this universe.|
a187|силы|1||1|3 обычные силы одного вида||Upgrade a force to rare.|
a188|силы|3||1|3 редкие силы одного вида||Upgrade a force to epic.|
a189|силы|6||1|3 эпические силы одного вида||Upgrade a force to legendary.|
a190|силы|1||1|улучш 3 силы|5|Upgrade 5 forces.|
a191|силы|1||1|8|13|Upgrade 13 forces.|
a192|силы|3||1|21|34|Upgrade 34 forces.|
a193|силы|3||1|55|89|Upgrade 89 forces.|
a194|силы|6||1|144|233|Upgrade 233 forces.|
a195|силы|6||1|377|610|Upgrade 610 forces.|
a196|силы|10||1|987|1597|Upgrade 1 597 forces.|Continue in the same spirit!
a197|силы|1||1|1|3|Upgrade 3 force chests.|
a198|силы|3||1|5|7|Upgrade 7 force chests.|
a199|силы|6||1|14|20|Upgrade 20 force chests.|
a200|силы|10||1|38|55|Upgrade 55 force chests.|Thanks for support!
"""
#endregion
+1
View File
@@ -0,0 +1 @@
uid://bx5onxr8atifn
@@ -1,3 +1,11 @@
class_name OwniverseApocalypseQueueLoader
static func get_data() -> Dictionary:
return CSVLoader.load(DATA)
#region Owniverse Items
const DATA := """
Num|Shortcut|Stage|Screen|Tick|Desc
1|LUB|1|3|50|Вселенский пузырь
2|LGW|1|3|5|Великая стена
@@ -130,3 +138,7 @@ Num|Shortcut|Stage|Screen|Tick|Desc
129|H|11|1|5|Водород
130|SMBH|12|2|5|СМЧД
131|S|12|1|5|Пространство
"""
#endregion
@@ -0,0 +1 @@
uid://8bh387weuy7a
+6 -6
View File
@@ -1008,7 +1008,7 @@ Trends.Report.Header|[b]Влияние развитых цивилизаций
Int.Fed.Report.Max|[Интеграция] Федерация: [color=#cccc88]+%value0%[/color]. Увеличено число цивилизаций, подверженных воздействию дипломатии.|max||||
Int.Enl.Report.Max|[Интеграция] Просвещение: [color=#cccc88]+%value0%[/color]. Ускорен прогресс на примитивных стадиях жизни.|max||||
Int.Ord.Report.Max|[Интеграция] Новый порядок: [color=#cccc88]+%value0%[/color]. Оказывается помощь и поддержка цивилизациям, попавшим в кризисные обстоятельства.|max||||
Emerged.Report.Header|[b]Максимальное число возникших сущностей[/b]|||||
Emerged.Report.Header|[b]Всего возникло сущностей[/b]|||||
Ctm.Report.Max|Трансцендентная цивилизация мистиков: [color=#cccc88]%value0%[/color]|max||||
Ctb.Report.Max|Трансцендентная цивилизация строителей: [color=#cccc88]%value0%[/color]|max||||
Ctd.Report.Max|Трансцендентная цивилизация дипломатов: [color=#cccc88]%value0%[/color]|max||||
@@ -1075,7 +1075,7 @@ HP.Report.Max|Обитаемая планета: [color=#cccc88]%value0%[/color]
RP.Report.Max|Каменистая планета: [color=#cccc88]%value0%[/color]|max||||
AB.Report.Max|Астероиды: [color=#cccc88]%value0%[/color]|max||||
GG.Report.Max|Газовый гигант: [color=#cccc88]%value0%[/color]|max||||
Produced.Report.Header|[b]Максимальное число созданных объектов[/b]|||||
Produced.Report.Header|[b]Всего создано объектов[/b]|||||
LUB.Report.Max|Вселенский пузырь: [color=#cccc88]%value0%[/color]|max||||
LGW.Report.Max|Великая стена: [color=#cccc88]%value0%[/color]|max||||
LGF.Report.Max|Галактическая нить: [color=#cccc88]%value0%[/color]|max||||
@@ -1116,7 +1116,7 @@ YD.Report.Max|Жёлтый карлик: [color=#cccc88]%value0%[/color]|max||||
OD.Report.Max|Оранжевый карлик: [color=#cccc88]%value0%[/color]|max||||
RD.Report.Max|Красный карлик: [color=#cccc88]%value0%[/color]|max||||
BD.Report.Max|Бурый карлик: [color=#cccc88]%value0%[/color]|max||||
Obtained.Report.Header|[b]Максимум за время существования вселенной[/b]|||||
Obtained.Report.Header|[b]Всего получено ресурсов[/b]|||||
S.Report.Max|Пространство: [color=#cccc88]%value0%[/color]|max||||
H.Report.Max|Водород: [color=#cccc88]%value0%[/color]|max||||
He.Report.Max|Гелий: [color=#cccc88]%value0%[/color]|max||||
@@ -2199,7 +2199,7 @@ Trends.Header|[b]Influence of advanced civilizations on the universe[/b]|||||
Int.Fed.Max|[Integration] Federation: [color=#cccc88]+%value0%[/color]. The number of civilizations affected by diplomacy is increased.|max||||
Int.Enl.Max|[Integration] Enlightenment: [color=#cccc88]+%value0%[/color]. The progress during primitive stages of life development is accelerated.|max||||
Int.Ord.Max|[Integration] New Order: [color=#cccc88]+%value0%[/color]. Civilizations in crisis obtain aid and support.|max||||
Emerged.Header|[b]Maximum amount of emerged entities[/b]|||||
Emerged.Header|[b]Total entities emerged[/b]|||||
Ctm.Max|Transcendent mystics civilization: [color=#cccc88]%value0%[/color]|max||||
Ctb.Max|Transcendent builders civilization: [color=#cccc88]%value0%[/color]|max||||
Ctd.Max|Transcendent diplomats civilization: [color=#cccc88]%value0%[/color]|max||||
@@ -2266,7 +2266,7 @@ HP.Max|Habitable Planet: [color=#cccc88]%value0%[/color]|max||||
RP.Max|Rocky Planet: [color=#cccc88]%value0%[/color]|max||||
AB.Max|Asteroids: [color=#cccc88]%value0%[/color]|max||||
GG.Max|Gas Giant: [color=#cccc88]%value0%[/color]|max||||
Produced.Header|[b]Maximum amount of created objects[/b]|||||
Produced.Header|[b]Total objects created[/b]|||||
LUB.Max|Universe Bubble: [color=#cccc88]%value0%[/color]|max||||
LGW.Max|Great Wall: [color=#cccc88]%value0%[/color]|max||||
LGF.Max|Galactic Filament: [color=#cccc88]%value0%[/color]|max||||
@@ -2307,7 +2307,7 @@ YD.Max|Yellow Dwarf: [color=#cccc88]%value0%[/color]|max||||
OD.Max|Orange Dwarf: [color=#cccc88]%value0%[/color]|max||||
RD.Max|Red Dwarf: [color=#cccc88]%value0%[/color]|max||||
BD.Max|Brown Dwarf: [color=#cccc88]%value0%[/color]|max||||
Obtained.Header|[b]Maximum for all the existence of the universe[/b]|||||
Obtained.Header|[b]Total resources obtained[/b]|||||
S.Max|Space: [color=#cccc88]%value0%[/color]|max||||
H.Max|Hydrogen: [color=#cccc88]%value0%[/color]|max||||
He.Max|Helium: [color=#cccc88]%value0%[/color]|max||||
+1 -1
View File
@@ -12,7 +12,7 @@ custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../../../Documents/Owad/Owniverse.I/Builds/owniverse.0.7.0-146.console.exe"
export_path="../../../Documents/Owad/Owniverse.I/Builds/owniverse.0.7.0-146.exe"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
+70 -7
View File
@@ -46,9 +46,9 @@ float. Весь порядок E27 сохраняет целые единицы.
### Общие окна рождения и эволюции
На всех скоростях полный цикл симуляции обрабатывает 0,2 реальной секунды: пять циклов на секунду запрошенного времени. До накопления полного интервала расчёт не запускается. Игровая длительность цикла равна `0.2 × 10^(speed - 1)` лет. При отставании размер шага не увеличивается, долг сохраняется. Незавершённая транзакция из сохранения возобновляется с прежней границей. Кооперативный бюджет кадра — 40 мс; частота отрисовки не определяет частоту полных циклов.
На всех скоростях целевая частота запуска — пять полных циклов в секунду. После накопления не менее 0,2 реальной секунды запускается расчёт за весь ожидающий игровой интервал. Время, поступающее во время расчёта, остаётся для следующего цикла. При задержках шаг увеличивается; время не отбрасывается. Смена скорости не сокращает реальный интервал между запусками. Незавершённая транзакция из сохранения возобновляется с прежней границей. Кооперативный бюджет кадра — 40 мс; фактическая частота завершения зависит от стоимости расчёта.
На скоростях 4–6 укрупнение звёздных партий зависит от размера цикла и прежнего окна рождения. Размер цикла — `0.2 × 10^(speed - 1)` игровых лет: 200, 2000 и 20000 лет. Если прежнее окно `max(lifespan / 40, временное разрешение)` шире цикла, оно сохраняется: долгоживущие звёзды не дробятся на более мелкую сетку. Если оно не шире цикла, новые звёзды группы `Star` (включая красные гиганты) объединяются по общей границе завершения жизни и переходят целиком.
На скоростях 4–6 укрупнение звёздных партий зависит от размера цикла и прежнего окна рождения. Номинальная ширина сетки партий — `0.2 × 10^(speed - 1)` игровых лет: 200, 2000 и 20000 лет. Если прежнее окно `max(lifespan / 40, временное разрешение)` шире цикла, оно сохраняется: долгоживущие звёзды не дробятся на более мелкую сетку. Если оно не шире цикла, новые звёзды группы `Star` (включая красные гиганты) объединяются по общей границе завершения жизни и переходят целиком.
При обработке существующих партий объединяются только две или более партии одного типа, весь оставшийся интервал превращения которых попадает в один и тот же цикл. Широкие партии и партии, пересекающие границу циклов, остаются без изменений. Количества и накопленные остатки складываются точно; объединённая партия переходит целиком на границе цикла. Повторное укрупнение не меняет результат.
@@ -117,7 +117,7 @@ GI не хранит накопленные счётчики первичной
Прогресс и деградация округляются вниз с сохранением остатков. Если оба накопленных исхода конкурируют за последнюю планету, распределяются только существующие объекты, а неиспользованный остаток переносится на следующую попытку. Деградация распределяется по весам `fall`; знаковые ошибки распределения также сохраняются.
Для всех шести стадий базовые доли за попытку — `progress=0.00025`, `degradation=0.0025`. Интервалы HP/EON1/EON2/EON3/EON4/EON5: 5 млн, 10 млн, 10 млн, 1 млн, 1 млн, 500 тыс. лет. Используются постепенные окна GI шириной `span / 40`. Случайные множители Unity не применяются. Агрессия, порядок и просвещение изменяют вероятности по формулам из раздела индикаторов ниже.
Базовые доли за попытку: для HP `progress=0.00025`, для EON1–EON5 `progress=0.025`; у всех `degradation=0.0025`. Текущие интервалы HP/EON1/EON2/EON3/EON4/EON5: 5 млн, 1 млн, 1 млн, 100 тыс., 100 тыс., 500 тыс. лет. Используются постепенные окна GI шириной `span / 40`. Случайные множители Unity не применяются. Агрессия, порядок и просвещение изменяют вероятности по формулам из раздела индикаторов ниже.
HP деградирует в RP. У эонов возможен откат к предыдущим стадиям по таблице. RP не развивается сам. C0.0 и C0.1 также развиваются: период 2000 лет, прогресс 0.0175, деградация 0.025. Переходы C0.0 → C0.1 → C0.2, откат C0.0 → EON5 и C0.1 → C0.0. Развитие C0.2 и выше подключено через этические правила. Конфликт civ_bonus устранён повторным импортом: бонусы структур совпадают с Unity. Для существующих этических цивилизаций пользователь выбрал равномерное распределение между шестью этиками с детерминированным целым остатком.
@@ -139,10 +139,20 @@ HP деградирует в RP. У эонов возможен откат к п
Колонизация агрегирует запросы по стадии назначения (на две стадии ниже отправителя), ограничивает их общим запасом GG, AB, RP и HP и заменяет ровно выделенное число планет. Целые колонии сохраняют владельца целевой планеты; состав получает дрейф 960/4/9. Исходное население не расходуется.
Применение потоков сначала строит таблицу исходных владельцев. Для каждой исходной стадии готовые целые квоты распределяются по строкам владельцев; последняя строка получает точный остаток. Стадии с единственным свободным запасом применяют готовые квоты напрямую. Поступления объединяются по стадии и владельцу; физическая выдача и статистика создания выполняются один раз на получателя. Ct всегда остаётся свободной от привязок.
При колонизации планеты сначала списываются по типам и владельцам, затем суммарное списание каждого владельца распределяется между стадиями колоний. Каждая стадия получает одну суммарную выдачу. Общие целые квоты, число списанных планет и количество колоний сохраняются точно. Из-за объединения распределений единичные остатки между владельцами могут отличаться от прежнего последовательного порядка; вероятности переходов и формулы этик не меняются.
В пределах цивилизационной транзакции точные общие количества передаются между фазами. Применение переходов, колонизация и коррекция ёмкости возвращают обновлённый снимок; перемещение между владельцами само по себе общую численность не меняет. Этот снимок используется для периодов, ограничений, нормализации этик и поклонения. После изменения запасов другими подсистемами снимок строится заново. Снимки не сохраняются в файл и не заменяют проверку состояния при внешних изменениях словарей.
Автоматическое создание нескольких типов звёзд также использует один текущий снимок. Успешная пробная операция вычитает фактически израсходованные количества; перенос ресурса в привязку сохраняет общий запас. При отказе пробной операции снимок остаётся неизменным. Новые звёзды выдаются после завершения отбора типов, как и прежде.
Космические стадии C2.0–C4.4 свободны от планетных привязок. Общие бюджеты звёздных семейств и структур учитывают уже занятые резервы. Неподдерживаемый промежуточный переход блокируется в операторе; конечные положительные запросы делят свободную ёмкость одновременно. Отклонённые количества возвращаются к источнику. Освободившаяся при переходах ёмкость доступна следующему шагу. Если внешние изменения уменьшили ёмкость ниже существующего населения, избыток последовательно деградирует с приоритетом старших стадий. При возвращении в планетные стадии используются реальные карликовые звёзды, при отсутствии хозяина население остаётся свободным.
Порядок полного шага: производство за интервал → непрерывные потоки существующих цивилизаций → поколения звёзд и биологической жизни и бласты → итоговые ограничения среды → тренды. Возникшие из C0.1 цивилизации C0.2 получают равномерный профиль, смешанный с существующим населением, и начинают непрерывную эволюцию со следующего шага. Разные размеры полного шага могут отличаться из-за этого порядка, ограничений среды и нелинейных эффектов; чистая непрерывная цепочка без ограничений согласована при разделении интервала.
Счётчик времени UI показывает запрошенные часы и обновляется с частотой до 10 Гц независимо от тяжёлой транзакции. Состояние мира и его индикаторы публикуются по завершении транзакции; при высокой нагрузке отображаемые часы поэтому могут немного опережать рассчитанные объекты. Тяжёлые циклы адаптивно запускаются с целевым интервалом 0,2–0,5 реальной секунды, каждый использует весь накопленный игровой интервал.
В `civilization_remainders` сохраняются квоты и дробные остатки потоков; в `civilization_stats` — войны, колонии, побочный ущерб и поклонение. Возрастные партии сохраняются для звёзд, HP, эонов, C0.0 и C0.1.
### Структуры и периоды
@@ -167,9 +177,11 @@ Ct не получает часы. Инициализация отсутству
- `1e-5 < density < 0.9`: обычная шкала, `ln(density × 1e5) / ln(9e4)`;
- `density ≥ 0.9`: шкала сжатия, `(density − 0.9) × 10`.
В Unity в первой формуле было `−7`; это ошибка, не позволяющая указателю двигаться. Здесь исправлено на `+7`. Переключение шкалы меняет и заголовок/описание индикатора. Само наступление апокалипсиса индикатор не запускает.
В Unity в первой формуле было `−7`; это ошибка, не позволяющая указателю двигаться. Здесь исправлено на `+7`. Переключение шкалы меняет и заголовок/описание индикатора.
Общая рамка `Background/WarningBorder` получает цвета Unity `WarningUI` в формате RGBA: при плотности ≤1e-5 — `C040407F`, ≤1e-6 — `DF2020BF`, ≤1e-7 — `FF0000FF`; при плотности ≥0,9 — `40A0C07F`, ≥0,97 — `20B0DFBF`, ≥1 — `00C0FFFF`. В нормальном диапазоне рамка прозрачна. Она обновляется вместе с индикаторами, не перехватывает нажатия и снимается при восстановлении плотности. Необратимая последовательность апокалипсиса Unity сюда не входит.
Общая рамка `Background/WarningBorder` получает цвета Unity `WarningUI` в формате RGBA: при плотности ≤1e-5 — `C040407F`, ≤1e-6 — `DF2020BF`, ≤1e-7 — `FF0000FF`; при плотности ≥0,9 — `40A0C07F`, ≥0,97 — `20B0DFBF`, ≥1 — `00C0FFFF`. В нормальном диапазоне рамка прозрачна. Она обновляется вместе с индикаторами и не перехватывает нажатия.
Пороговые состояния управляют запуском апокалипсиса: при `density ≤ bigrip3` начинается Big Rip, при `density ≥ bigcrunch3` — Big Crunch. Последовательность берётся из `Global.ApocalypseQueue`, сортируется по `Num` и использует указанные `Stage`, `Screen` и `Tick`. Каждые 0,1 секунды активные объекты выбранной страницы теряют половину количества (для остатков до 5 — по одному). На время последовательности скорость фиксируется на 1, ручное и автоматическое создание блокируется, а экран переключается по очереди. Big Rip пропускает `S`, Big Crunch — `SMBH`; остаток энергии постепенно переводится в соответствующий объект. После 20 тиков с нулевой энергией апокалипсис получает фазу завершения, симуляция останавливает время. Фаза, тип события, предупреждение и позиция в очереди сохраняются в состоянии вселенной.
### Горючесть, взрывчатость и агрессия
@@ -222,10 +234,61 @@ CSVLoader разбирает строки, колонка `param export` пре
Начальная энергия относится к новым вселенным; запас энергии существующей
вселенной остаётся частью её сохранения.
G, C, PI, helium_multiplier, helium_bonus_limit, space_generation, ChatPerTick
и параметры ForceBox загружаются, но пока не используются в GI: соответствующие
G, C, space_generation, ChatPerTick и параметры ForceBox загружаются, но пока не
используются в GI: соответствующие
формулы и механики старой Unity-версии здесь отсутствуют. Производство объектов
в GI задаётся их собственной таблицей life_product и lifespan.
Для звёзд с прямой стоимостью H доля He в свободной смеси H+He заменяет часть
водородной стоимости. Применяются `helium_multiplier` и `helium_bonus_limit`, а
замещение ограничено 50%, как в Unity. Недостающий He не блокирует создание:
непокрытая часть оплачивается H. При создании таких звёзд срок жизни
умножается на `1 − 1.5 × доля замещения`; тот же коэффициент задаёт сроки их
партий. Автоматическое создание использует свободный He, а бюджет автоматического
H списывает только оставшуюся после замещения водородную часть.
`tests/test_extra_params.gd` проверяет загрузку точных чисел и изменение энергии,
плотности, объёма пустот, порогов индикатора и бонуса горючести.
## Достижения и уведомления
`OwniverseAchievementLoader` задаёт локализованные достижения. Строки с
`Ignore=1` исключены из проверок, списка и общего количества. Для включённых
строк поддерживаются `age::порог` и `total:ID:порог`. `total` — накопленные
поступления `stats.total_amount`, включая автоматическое производство и
эволюцию; расход и гибель объектов не уменьшают этот счётчик. Возраст берётся
из завершённого состояния симуляции. Пороги разбираются точной арифметикой.
`ui/achievements/achievement_controller.gd` проверяет условия при смене
ревизии состояния и после создания новой вселенной. Пустое OpenCondition
делает достижение видимым без уведомления; непустое открывает его с серой
иконкой `g`. Выполнение AchieveCondition выдаёт цветную иконку `c`. Если оба
условия выполнены одновременно, показывается только цветное уведомление.
Уже полученные достижения повторно не выдаются.
Состояния видимости и получения сохраняются отдельно в
`user://achievements.cfg` и сохраняются между вселенными. Очередь уведомлений
не сохраняется. Единая панель поверх индикаторов следует за выбранной
страницей: показ 3 секунды, затем затухание 0,3 секунды. Меню приостанавливает
таймер. Клик или касание закрывает текущее уведомление; очередь сохраняется. После показа индикаторы возвращаются. Тексты списка и уведомления
обновляются при смене языка; список прокручивается.
`tests/test_achievements.gd` проверяет все включённые пороги, фильтр Ignore,
открытие и получение, отсутствие повторных уведомлений, паузу в меню,
возврат индикаторов, сохранение прогресса и локализацию. Файл прогресса теста
создаётся отдельно в `/tmp`.
## Отчёт о вселенной
`ui/report/report.gd` форматирует готовое состояние при открытии
экрана отчёта и смене языка. Результат хранится в `Report/ReportBody`.
Периодического пересчёта отчёта и новых счётчиков симуляции нет.
Доли восьми типов звёзд учитывают звёзды в структурах. Строки количеств
используют накопленные `stats.total_amount` с существующими алиасами;
заголовки обозначают суммарное получение, а не исторические максимумы.
Эффекты интеграции показывают прирост готовых параметров Federation,
Enlightenment и Order относительно начального значения 1.
Нулевые строки и пустые разделы скрыты. Количества выводятся полностью,
с пробелами между группами по три цифры. Тексты и заголовок поддерживают RU/EN.
+64 -6
View File
@@ -7,6 +7,7 @@ const SPEED_TEXTURE_NAME_TEMPLATE = "res://ui/topmenu/time&speed/time%s.png"
const ItemDescription = preload("res://world/owniverse/item_description.gd")
const WorldIndicators = preload("res://ui/indicators/world_indicators.gd")
var achievements: Node
var world_indicators: RefCounted
var indicator_elapsed: float = 0.0
var indicator_revision: int = -1
@@ -98,7 +99,14 @@ var g_active_items: Dictionary = {}
var pending_page: int = -1
var active_actions_dirty: bool = true
var active_actions_elapsed: float = 0.0
var clock_display_elapsed: float = 0.0
var interface_sounds_enabled: bool = false
var apocalypse_elapsed: float = 0.0
var apocalypse_ui_phase: int = -1
var apocalypse_screen: int = -1
@onready var time_minus_button: TextureButton = $World/TopMenu/TimeMinus
@onready var time_plus_button: TextureButton = $World/TopMenu/TimePlus
@onready var apocalypse_music: OwniverseMusicPlayer = $"../MusicPlayer"
var owniverse:Owniverse = Owniverse.new()
var owniverse_items: Dictionary[String, OwniverseItem] = {}
@@ -273,6 +281,7 @@ func _swipe_to_nearest_page() -> void:
func _input(event: InputEvent) -> void:
if E.eq(owniverse.state.get("apocalypse", {}).get("phase", 0), 1): return
if event is InputEventScreenDrag:
#effects_player.play_effect("swipe")
stage_page_scroller.scroll_horizontal = clamp(
@@ -298,7 +307,7 @@ func _set_active_page_actions() -> void:
#var amount = owniverse.get_available_to_produce(item_id)
#var amount = owniverse.get_available_to_produce(entity)
var amount = owniverse.production.calculate_max_producible(entity)
var amount = (0 if E.gt(owniverse.state.get("apocalypse", {}).get("phase", 0), 0) else owniverse.production.calculate_max_producible(entity))
#print(item_id, " : ", amount)
if item_id == "SMBH":
var preview: Callable = func(requested):
@@ -315,7 +324,11 @@ func _page_action_pressed(page_id: int, action_amount) -> void:
var item_id: String = g_active_items[page_id].item_id
if E.gt(action_amount, 0):
owniverse.request_production(item_id, action_amount)
if owniverse.request_production(item_id, action_amount):
page_actions[page_id].mark_pending(action_amount)
func _on_owniverse_production_applied(_item_id: String) -> void:
active_actions_dirty = true
#endregion
@@ -327,6 +340,10 @@ func _ready() -> void:
create_new_universe()
load_data()
_refresh_universe_ui()
achievements = preload("res://ui/achievements/achievement_controller.gd").new()
add_child(achievements)
achievements.initialize(self)
achievements.update(0.0)
g_counters.swipe.sec = 0.2
@@ -345,6 +362,7 @@ func _enable_interface_sounds() -> void:
func create_new_universe() -> void:
var previous: Owniverse = owniverse
owniverse = Owniverse.new()
owniverse.production_applied.connect(_on_owniverse_production_applied)
var inited_result: Dictionary = init_enitities()
locked_items = inited_result.locked_items
@@ -384,7 +402,7 @@ func create_new_universe() -> void:
func _refresh_universe_ui() -> void:
owniverse.presentation.refresh(owniverse.state, owniverse.catalog)
_change_speed(0)
set_datetime(owniverse.state.time.year)
set_datetime(owniverse.display_clock().year)
set_owniverse_item_value()
_unlock_items()
if world_indicators != null: world_indicators.update(owniverse)
@@ -392,6 +410,12 @@ func _refresh_universe_ui() -> void:
func _process(delta: float) -> void:
owniverse.evolute_frame(delta, get_tree())
_process_apocalypse(delta)
if achievements != null: achievements.update(delta)
clock_display_elapsed += delta
if clock_display_elapsed >= 0.1:
clock_display_elapsed = fmod(clock_display_elapsed, 0.1)
set_datetime(owniverse.display_clock().year)
indicator_elapsed += delta
if indicator_elapsed >= 0.1 and indicator_revision != owniverse.presentation.revision:
indicator_elapsed = 0.0
@@ -399,9 +423,9 @@ func _process(delta: float) -> void:
indicator_revision = owniverse.presentation.revision
_update_descriptions(delta)
if not owniverse.presentation.pending_amounts.is_empty(): set_owniverse_item_value()
if displayed_year == null or E.ne(displayed_year, owniverse.state.time.year):
set_datetime(owniverse.state.time.year)
displayed_year = owniverse.state.time.year
var committed_year: Variant = owniverse.state.time.year
if displayed_year == null or E.ne(displayed_year, committed_year):
displayed_year = committed_year
#owniverse.cycle(delta)
@@ -421,6 +445,7 @@ func _process(delta: float) -> void:
func _change_speed(delta: int) -> void:
if E.eq(owniverse.state.get("apocalypse", {}).get("phase", 0), 1) and delta != 0: return
var previous_speed: int = owniverse.speed
owniverse.speed = owniverse.speed + delta
@@ -448,6 +473,39 @@ func _change_speed(delta: int) -> void:
g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed + 1))
func _process_apocalypse(delta: float) -> void:
var apocalypse: Dictionary = owniverse.state.get("apocalypse", {})
var phase: int = int(apocalypse.get("phase", 0))
if phase != apocalypse_ui_phase:
apocalypse_ui_phase = phase
if phase == 1:
apocalypse_music.play_apocalypse()
owniverse.speed = 1
_change_speed(0)
elif phase == 2:
owniverse.speed = 1
_change_speed(0)
time_minus_button.disabled = phase == 1
time_plus_button.disabled = phase == 1
active_actions_dirty = true
if phase == 1:
_set_active_page_actions()
if phase != 1:
apocalypse_elapsed = 0.0
return
apocalypse_elapsed += delta
if owniverse.frame_busy or owniverse.production_queue_busy:
return
while apocalypse_elapsed >= OwniverseApocalypseController.TICK_SECONDS and E.eq(owniverse.state.apocalypse.phase, 1):
apocalypse_elapsed -= OwniverseApocalypseController.TICK_SECONDS
var result: Dictionary = owniverse.apocalypse.process_tick(owniverse.state)
if int(result.screen) >= 0 and int(result.screen) != apocalypse_screen:
apocalypse_screen = int(result.screen)
stage_page_scroller.scroll_horizontal = apocalypse_screen * PAGE_WIDTH
if bool(result.finished):
_change_speed(0)
func _on_topmenu_button_pressed(screen_name: String) -> void:
var current_screen: String = ""
for screen_id in screens:
+23 -9
View File
@@ -238,14 +238,33 @@ godot --path Owniverse.GI --script res://tests/benchmark_civilizations.gd -- ui
## Расчёт между кадрами
`test_flow_commit.gd` проверяет консолидированное применение переходов и колонизации: точные квоты получателей, сохранение количества у каждого владельца, профили этик, статистику создания и отсутствие привязок у Ct. Проверки используют несколько владельцев и численность 103, 10^40 + 103 и 10^400 + 103.
Дополнительно проверяется точное совпадение обновляемых общих количеств с полным пересчётом после переходов, колонизации и каскадной деградации при нехватке ёмкости. Входные снимки остаются неизменными.
`test_production_transaction.gd` проверяет, что успешное создание обновляет общий снимок, привязка сохраняет общий ресурс, а отклонённая операция сохраняет и состояние, и снимок.
```sh
godot --headless --path Owniverse.GI --script res://tests/test_flow_commit.gd
```
`godot --headless --path Owniverse.GI --script res://tests/test_frame_evolution.gd`
Stage вызывает `evolute_frame`: транзакция получает бюджет 40 мс и уступает
следующему кадру внутри создания партий и обработки стадий. Расчёт остаётся
на главном потоке, состояние публикуется только после полного завершения.
Одна транзакция обрабатывает 0,2 реальной секунды на любой скорости. Запрошенное время хранится отдельно, поэтому отставание не превращается
в один всё более тяжёлый шаг. При предельной нагрузке время симуляции может
отставать от запрошенного; это не ограничение FPS интерфейса и не удаление событий.
Запрошенные игровые часы и счётчик UI обновляются независимо от тяжёлого расчёта;
Stage показывает запрошенное время раз в 0,1 секунды (10 Гц), а опубликованное
состояние мира остаётся на последнем завершённом цикле. Расчёт запускается после
накопления выбранного интервала и обрабатывает весь ожидающий интервал. Время,
поступившее во время расчёта, обрабатывается следующим циклом. Контроллер нагрузки
меняет целевой шаг между 0,2, 0,25, 0,333 и 0,5 секунды (5/4/3/2 цикла в секунду):
вниз при EMA расчёта выше 90% текущего шага, вверх после 30 циклов ниже 65%.
Временное разрешение укрупнения партий масштабируется с шагом, исходная точность
сохраняется на 5 Гц. Контроллер не пропускает накопленное игровое время, но 2/с —
нижняя целевая частота, не гарантия при перегрузке.
`benchmark_dynamic_cadence.gd` сравнивает пропускную способность полного цикла на двух диагностических сохранениях. `benchmark_dynamic_cadence_ui.gd` сравнивает 10 секунд работы полной сцены на сохранении со всеми объектами; результаты сохранены в `artifacts/dynamic_cadence_results.json` и `artifacts/dynamic_cadence_ui_results.json`.
Производство во время транзакции выполняется после её завершения. Запрошенное
время, активный интервал, скорость и очередь производства входят в сохранение;
@@ -292,12 +311,7 @@ Stage вызывает `evolute_frame`: транзакция получает б
1.057. Замер включает сокращение накопленного отставания сохранения;
это результат конкретного сценария на Mac, а не гарантия для Android.
Полный шаг фиксирован: 0,2 реальной секунды на всех скоростях, пять циклов
на секунду запрошенного времени. Неполный интервал накапливается. При отставании
шаг сохраняется, долг не отбрасывается. Возобновляемая транзакция сохраняет
границу из checkpoint. Бюджет кадра остаётся 40 мс. `test_frame_evolution`
проверяет расписание всех шести скоростей, сохранение долга и пять циклов
на одну секунду.
Целевая частота запуска — пять циклов в секунду. Размер шага равен всему накопленному интервалу. Checkpoint сохраняет границу активного расчёта и ожидающее реальное время. Тест проверяет все шесть скоростей, смену скорости, задержку запуска и накопление времени во время расчёта.
Дипломатический drift использует шесть взвешенных вкладов вместо матрицы
пар этик: вес = сумма вкладов + 195 × свой вклад − противоположный вклад.
@@ -0,0 +1,49 @@
{
"date": "2026-10-08",
"machine": "Apple M4 Pro",
"godot": "4.7.2.stable.official.ed1daf0bf",
"renderer": "headless",
"delta_seconds": 0.2,
"profile_enabled": true,
"ui": false,
"adaptation": {
"controller": "runtime-only exponential moving average of full-cycle CPU time; 0.8 previous + 0.2 latest",
"quality_thresholds_ms": {
"quality_1": 200,
"quality_2": 220,
"quality_3": 300,
"quality_2_to_3_when_over_ms": 200,
"recovery_below_ms": 140,
"recovery_cycles": 30
},
"quality_effects": {
"quality_1": "one-transition civilization approximation, at most 3 stellar/life generations per cycle, temporal grouping x2",
"quality_2": "same civilization approximation, at most 1 generation per cycle, temporal grouping x4",
"quality_3": "same civilization approximation, at most 1 generation per cycle, temporal grouping x8, alternate halves of batch types each cycle"
}
},
"inputs": {
"all_objects_speed6.adventure.json": "44bee3b3753fa51615f766d8f800a10cc2b70c8e7fb4ea12b6e18da7f07a7d67",
"many_objects_fragmented_stars_cycle.adventure.json": "1e95ef398923f26b912a305c84f9b3eb502d24cb14e8ae687a4becccf047324a"
},
"runs": [
{"speed": 1, "save": "all_objects_speed6.adventure.json", "cycles": 60, "mean_ms": 165.03665, "min_ms": 151.514, "max_ms": 170.171, "cycles_per_second": 6.05926, "game_years": 12, "validation": "OK"},
{"speed": 1, "save": "many_objects_fragmented_stars_cycle.adventure.json", "cycles": 60, "mean_ms": 128.463817, "min_ms": 124.475, "max_ms": 144.05, "cycles_per_second": 7.784293, "game_years": 12, "validation": "OK"},
{"speed": 2, "save": "all_objects_speed6.adventure.json", "cycles": 60, "mean_ms": 181.431217, "min_ms": 171.92, "max_ms": 196.005, "cycles_per_second": 5.511731, "game_years": 120, "validation": "OK"},
{"speed": 2, "save": "many_objects_fragmented_stars_cycle.adventure.json", "cycles": 60, "mean_ms": 131.78055, "min_ms": 126.963, "max_ms": 148.01, "cycles_per_second": 7.588373, "game_years": 120, "validation": "OK"},
{"speed": 3, "save": "all_objects_speed6.adventure.json", "cycles": 60, "mean_ms": 175.235683, "min_ms": 166.031, "max_ms": 203.456, "cycles_per_second": 5.7066, "game_years": 1200, "validation": "OK"},
{"speed": 3, "save": "many_objects_fragmented_stars_cycle.adventure.json", "cycles": 60, "mean_ms": 145.013367, "min_ms": 135.062, "max_ms": 162.768, "cycles_per_second": 6.895916, "game_years": 1200, "validation": "OK"},
{"speed": 4, "save": "all_objects_speed6.adventure.json", "cycles": 60, "mean_ms": 159.705883, "min_ms": 150.404, "max_ms": 221.101, "cycles_per_second": 6.26151, "game_years": 12000, "validation": "OK"},
{"speed": 4, "save": "many_objects_fragmented_stars_cycle.adventure.json", "cycles": 60, "mean_ms": 177.064483, "min_ms": 170.624, "max_ms": 189.729, "cycles_per_second": 5.64766, "game_years": 12000, "validation": "OK"},
{"speed": 5, "save": "all_objects_speed6.adventure.json", "cycles": 60, "mean_ms": 174.857517, "min_ms": 158.809, "max_ms": 270.078, "cycles_per_second": 5.718942, "game_years": 120000, "validation": "OK"},
{"speed": 5, "save": "many_objects_fragmented_stars_cycle.adventure.json", "cycles": 60, "mean_ms": 185.601233, "min_ms": 131.409, "max_ms": 273.8, "cycles_per_second": 5.387895, "game_years": 120000, "validation": "OK"},
{"speed": 6, "save": "all_objects_speed6.adventure.json", "cycles": 60, "mean_ms": 190.570967, "min_ms": 159.38, "max_ms": 345.149, "cycles_per_second": 5.247389, "game_years": 1200000, "validation": "OK"},
{"speed": 6, "save": "many_objects_fragmented_stars_cycle.adventure.json", "cycles": 60, "mean_ms": 184.426, "min_ms": 149.645, "max_ms": 366.767, "cycles_per_second": 5.422229, "game_years": 1200000, "validation": "OK"}
],
"notes": [
"Each run begins in exact mode and adapts during its 60-cycle series.",
"The mean meets 5 complete cycles/s at all six speeds in these synthetic diagnostic saves on this computer; maxima show occasional cycles still exceed 200 ms at speeds 3-6.",
"No Android device or UI performance was measured.",
"Diagnostic save inputs were not modified."
]
}
@@ -379,3 +379,140 @@ godot --headless --path . --script res://tests/benchmark_large_star_cohorts.gd -
```sh
godot --headless --path . --script res://tests/benchmark_many_objects.gd -- cycles=40 repeats=2
```
## Консолидация применения потоков и колонизации — 2026-10-08
Первый этап: исходное население распределяется по строкам владельцев одной таблицы переходов. Свободные стадии применяют готовые целые квоты напрямую. При колонизации списания планет объединяются по владельцам; каждая стадия колоний получает одну суммарную выдачу. Общие целые количества и статистика сохранены. Изменён порядок распределения единичных остатков между владельцами; полного совпадения состояния с прежним порядком округления не заявляется. Ct остаётся свободной от привязок.
До и после загружались одни и те же диагностические файлы с теми же количествами и партиями. Скорость 6, полный интервал 0,2 секунды (20 000 игровых лет), CPU-профилирование включено, UI отсутствует. По два прогона из исходного состояния, каждый по 20 циклов: до → после → после → до. На каждую модель и сохранение обработано 800 000 игровых лет. Валидация начальных и конечных состояний прошла; SHA-256 входных файлов до и после совпали. Сохранение игрока не использовалось.
| Сохранение | До, мс | После, мс | Сокращение времени | Полных циклов/с после |
|---|---:|---:|---:|---:|
| Все объекты | 385,263 | 377,222 | 2,09% | 2,651 |
| Много объектов и партий | 506,888 | 443,303 | 12,54% | 2,256 |
Для тяжёлого сохранения:
| Участок | До, мс/цикл | После, мс/цикл |
|---|---:|---:|
| Применение переходов | 113,011 | 91,591 |
| Колонизация | 53,487 | 11,520 |
| Подготовка потоков | 114,808 | 114,742 |
| Поколения звёзд и жизни | 116,085 | 115,999 |
Первый этап сокращает полный тяжёлый цикл на 12.54%, но цель пяти циклов в секунду не достигнута. До 200 мс нужно ещё примерно 2.22-кратное ускорение. Следующие крупные затраты — подготовка цивилизационных потоков, поколения и оставшееся применение переходов.
Проверки: целочисленные квоты и владельцы, включая 10^400 + 103 — 1068; цивилизационные потоки — 1895; состояние цивилизаций — 70; кадровый планировщик — 38. Всего 3071 проверка без ошибок. Проверены сохранение населения каждого владельца при планетных переходах, точные поступления по стадиям, замена планет колониями, статистика и Ct.
Полные профили, отдельные прогоны и контрольные суммы: `flow_commit_step1_results.json`. Базовая версия: `413f2be9044090c878c2909dd9d0646f77513a5f`.
Для замера текущей версии на тех же входных файлах:
```sh
godot --headless --path . --script res://tests/benchmark_civilization_flows.gd -- res://tests/artifacts/all_objects_speed6.adventure.json 0.2 profile cycles=20
godot --headless --path . --script res://tests/benchmark_civilization_flows.gd -- res://tests/artifacts/many_objects_fragmented_stars_cycle.adventure.json 0.2 profile cycles=20
```
## Повторное использование общих количеств — 2026-10-08
Второй этап: общий точный снимок передаётся между фазами цивилизационных потоков, колонизации и коррекции ёмкости. Переходы и выдачи обновляют только затронутые количества; перенос между владельцами сохраняет общую численность. Периоды стадий, дрейф и поклонение используют актуальные количества этого интервала. Автоматическое создание разных типов звёзд также использует общий снимок: успешная пробная операция вычитает расходы, отказ сохраняет исходный снимок. При внешних изменениях используется обычный пересчёт. Снимки не записываются в сохранения.
Сравнение с версией после первого этапа. На двух одинаковых диагностических сохранениях выполнены пары до → после → после → до: по 20 циклов на прогон, по 40 циклов на модель и сохранение. Скорость 6, интервал 0,2 секунды, CPU-профиль включён, UI отсутствует. Каждый прогон полностью обработал 400 000 игровых лет. Состояния валидны, входные SHA-256 не изменились.
| Сохранение | До, мс | После, мс | Сокращение | Полных циклов/с после |
|---|---:|---:|---:|---:|
| Все объекты | 377,185 | 367,326 | 2,61% | 2,722 |
| Много объектов и партий | 443,163 | 422,973 | 4,56% | 2,364 |
Тяжёлое сохранение:
| Метрика | До | После |
|---|---:|---:|
| Полных сборок общих количеств за цикл | 15 | 7 |
| Сборка общих количеств, мс/цикл | 37,498 | 17,767 |
| Производство, мс/цикл | 37,554 | 20,316 |
| Применение потоков, мс/цикл | 91,626 | 89,259 |
| Завершение потоков, мс/цикл | 9,821 | 6,881 |
Время сборки общих количеств вложено в остальные фазы и не прибавляется к ним повторно. Проверка цивилизационной части отдельно дала небольшой выигрыш; основную экономию обеспечивает исключение повторных сборок при автоматическом создании звёзд.
Полные состояния обеих версий совпали после 10 последовательных сравнений: по пять на каждое сохранение, интервалы 0,2 / 0,53 / 0,31 / 0,2 / 0,2 секунды. Дополнительные проверки сравнивают снимки с полным пересчётом, контролируют неизменность входных снимков, отказ производства и каскадную деградацию. Выполнено 4321 целевая проверка без ошибок; полная регрессия всех наборов не запускалась.
Пять полных циклов в секунду пока не достигнуты: тяжёлый цикл составляет 423.0 мс; до 200 мс нужно ещё около 2.11-кратного ускорения. Следующая крупная статья — подготовка потоков цивилизаций, около 115.2 мс/цикл.
Полные результаты и контрольные суммы: `physical_totals_step2_results.json`. Базовая версия: `9357f757a95db96274d4e115c7e498b58eca6902`. Сохранение игрока не использовалось.
## Упрощение расчёта наград потоков цивилизаций — 2026-10-08
Третий этап убирает промежуточное динамическое число при умножении точного целого населения на float-ставку награды. Частное и дробный остаток вычисляются напрямую, а дробный перенос сохраняет прежнее округление. Это применяется к наградам войны, урона и колонизации; размеры населения остаются точными целыми.
Сравнение с коммитом `c429403` (после второго этапа): те же два диагностических сохранения, скорость 6, интервал 0,2 секунды, CPU-профиль включён. Порядок пар — до → после → после → до; два прогона по 20 циклов на версию и сохранение. Каждый прогон обработал 400 000 игровых лет, валидация сохранений прошла, SHA-256 входов совпали. Сохранение игрока не использовалось.
| Сохранение | До, мс | После, мс | Сокращение времени | Полных циклов/с после |
|---|---:|---:|---:|---:|
| Все объекты | 380,579 | 357,026 | 6,19% | 2,801 |
| Много объектов и партий | 432,912 | 409,268 | 5,46% | 2,443 |
Подготовка потоков уменьшилась с 111,980 до 87,832 мс на сохранении со всеми объектами и со 117,004 до 92,100 мс на тяжёлом сохранении — примерно на 21% в обоих случаях. Остальные основные участки остались в пределах погрешности замера.
Полные состояния до и после совпали после пяти последовательных интервалов на каждом сохранении (включая дробные интервалы). Тест оптимизаций прошёл: 1889 проверок без ошибок. Цель пяти полных циклов в секунду ещё не достигнута: тяжёлому циклу требуется ускорение примерно в 2,05 раза, чтобы уложиться в 200 мс.
Полные профили и SHA-256 входов: `civilization_flow_prepare_step3_results.json`.
## Проверка оптимизации частичного изъятия партий — 2026-10-08
Перенос связанного содержимого уже агрегируется по типам внутри поколения. Проверен вариант частичного изъятия, исключающий промежуточное сложение квоты с дробным переносом и последующее вычитание целой части. Возрастные интервалы и порядок поколений сохранялись.
Парный замер на тех же сохранениях: скорость 6, интервал 0,2 секунды, CPU-профиль, без UI; до → после → после → до, по 20 циклов на прогон (40 циклов на версию и сохранение).
| Сохранение | До, мс/цикл | После, мс/цикл |
|---|---:|---:|
| Все объекты | 350,516 | 349,835 |
| Много объектов и партий | 404,255 | 404,390 |
Изъятие партий на тяжёлом сохранении: 40,919 → 40,837 мс/цикл. Изменения полного цикла меньше 0,2%; подтверждённого выигрыша нет. Вариант **откачен**, код симуляции оставлен прежним. Полные состояния совпали в 10 последовательных сравнениях; границы партий — 117 проверок без ошибок. Входные файлы не изменены.
Результаты отклонённого варианта: `batch_withdrawal_step4_results.json`. Этот замер не доказывает достижение пяти полных циклов в секунду.
## Адаптивное упрощение симуляции — 2026-10-08
Добавлен runtime-контроллер качества, который измеряет сглаженное CPU-время полного цикла. Он повышает уровень упрощения при превышении бюджета 200 мс и снимает его после 30 циклов с запасом. Режим не сохраняется в файл: после загрузки точность постепенно подстраивается заново.
При нагрузке цивилизационный поток заменяется приближением не более чем с одним переходом на стадию за интервал. На наиболее грубом уровне типы возрастных партий делятся на две чередующиеся группы: каждая обрабатывается раз в два цикла. Временные группы при этом расширяются. Это сохраняет количества, но сдвигает сроки отдельных переходов и исключает несколько последовательных переходов цивилизации за один интервал.
Проверены два синтетических сохранения на Apple M4 Pro, Godot 4.7.2, headless, с CPU-профилем и интервалом 0,2 секунды. Каждый замер — 60 последовательных полных циклов; каждый запуск начинается в точном режиме и адаптируется по ходу серии.
| Скорость | Все объекты, мс/цикл (циклов/с) | Много объектов и партий, мс/цикл (циклов/с) |
|---:|---:|---:|
| 1 | 165,0 (6,06) | 128,5 (7,78) |
| 2 | 181,4 (5,51) | 131,8 (7,59) |
| 3 | 175,2 (5,71) | 145,0 (6,90) |
| 4 | 159,7 (6,26) | 177,1 (5,65) |
| 5 | 174,9 (5,72) | 185,6 (5,39) |
| 6 | 190,6 (5,25) | 184,4 (5,42) |
Среднее время на всех прогонах ниже 200 мс. Отдельные циклы всё ещё бывают длиннее: максимумы составили 203–367 мс на скоростях 3–6, включая первые циклы до срабатывания адаптации. Значит, замеры подтверждают требуемую среднюю пропускную способность на этом компьютере, но не гарантируют отсутствие кратковременных задержек. Android и работа с UI отдельно не проверялись.
Проверки: адаптивное качество — 18, хронология поколений — 28, границы партий — 117, оптимизации симуляции — 1889, планировщик кадров — 39; ошибок нет. Все 12 конечных диагностических состояний прошли валидацию. Хеши входных файлов: `44bee3b3753fa51615f766d8f800a10cc2b70c8e7fb4ea12b6e18da7f07a7d67` и `1e95ef398923f26b912a305c84f9b3eb502d24cb14e8ae687a4becccf047324a`.
Детали уровней, минимумы/максимумы и воспроизводимые результаты: `adaptive_quality_results.json`. Штатный benchmark принимает параметр `speed=1..6`.
## Адаптивная частота циклов 5 → 4 → 3 — 2026-10-17
Частота запуска полного цикла теперь снижается при устойчивой нагрузке: пороги шага — 0,2, 0,25 и 0,333 секунды. EMA времени расчёта переключает частоту вниз, когда цикл занимает больше 90% текущего интервала; возврат на одну ступень происходит после 30 циклов быстрее 65% интервала. Уровень частоты временный и после загрузки начинается с 5 Гц. Игровые часы продолжают накапливать весь прошедший интервал. Временное объединение партий масштабируется с выбранным интервалом, при этом при 5 Гц прежняя точность остаётся прежней.
Проверка сцены на диагностическом сохранении `all_objects_speed6.adventure.json`, скорость 6, Godot 4.7.2, Mac16,8. Сравнивались фиксированные 5 Гц и адаптивные 5/4/3 Гц, два прогона каждого режима в чередующемся порядке, по 10 секунд реального времени. Последняя транзакция каждого прогона завершалась до снятия результата.
| Метрика | Фиксированные 5 Гц | Адаптивно, установилось 3 Гц | Изменение |
|---|---:|---:|---:|
| Завершённые циклы/с | 4,64 | 2,96 | −36% |
| Средний FPS сцены | 22,60 | 64,35 | ×2,85 |
| p95 времени кадра | 115,6 мс | 66,5 мс | −42% |
| Средний максимум кадра | 227 мс | 205 мс | −10% |
| Опубликованные игровые годы/с | 99 017 | 98 049 | −1,0% |
На этих замерах реальное игровое время почти не замедлилось: симуляция завершала около трёх более крупных циклов вместо примерно 4,6 меньших. Средний FPS заметно вырос, но отдельные кадры всё ещё превышали 200 мс; это не гарантия отсутствия коротких задержек. UI-замер выполнен в headless-сцене на компьютере, поэтому его FPS не переносится напрямую на телефон.
Отдельно прогнан синхронный benchmark полного CPU-цикла на тех же двух тяжёлых сохранениях (скорость 6, 30 циклов × 2 повтора). При фиксированном шаге 0,2 секунды и адаптивном шаге итоговая обработка игрового времени за CPU-секунду выросла с 1,099 до 1,656 на сохранении со всеми объектами (+50,7%) и с 1,036 до 1,697 на сохранении со множеством объектов и партий (+63,9%). В каждом адаптивном прогоне частоты установились на 3 Гц; интервалы игровых часов сверены с запрошенными, все конечные состояния прошли проверку сохранения. Синхронный показатель показывает пропускную способность расчёта, а UI-прогон — фактический темп транзакций сцены.
Повторяемые результаты: `dynamic_cadence_results.json` (CPU-прогоны) и `dynamic_cadence_ui_results.json` (сцена и UI). Запуски выполняются скриптами `benchmark_dynamic_cadence.gd` и `benchmark_dynamic_cadence_ui.gd`; исходные диагностические сохранения не изменялись.
@@ -0,0 +1,646 @@
{
"retained": false,
"conclusion": "No measurable full-cycle improvement; implementation reverted",
"delta_seconds": 0.2,
"exact_state_differential": true,
"input_sha256": {
"all_objects_speed6": "44bee3b3753fa51615f766d8f800a10cc2b70c8e7fb4ea12b6e18da7f07a7d67",
"many_objects_fragmented_stars_cycle": "1e95ef398923f26b912a305c84f9b3eb502d24cb14e8ae687a4becccf047324a"
},
"order": "before-after-after-before",
"runs": [
{
"cycles": 20,
"max_ms": 382.054,
"mean_ms": 347.474,
"min_ms": 291.952,
"model": "before",
"profile": {
"assign_homes": [
23851,
80
],
"civilization_flow_capacity": [
121403,
20
],
"civilization_flow_colonies": [
52415,
20
],
"civilization_flow_finish": [
135271,
20
],
"civilization_flow_ledger": [
880672,
20
],
"civilization_flow_plan": [
1718363,
20
],
"civilization_operator_build": [
61955,
20
],
"civilization_operator_solve": [
673740,
860
],
"enforce_capacity": [
316094,
80
],
"generation_products": [
190666,
40
],
"generation_withdrawal": [
545822,
60
],
"generations": [
2727124,
20
],
"initialize": [
113986,
20
],
"newborn_batches": [
121039,
3016
],
"physical_amounts_build": [
285729,
200
],
"production": [
373910,
20
],
"trends": [
358046,
20
]
},
"run": 1,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 382.156,
"mean_ms": 349.53305,
"min_ms": 297.266,
"model": "after",
"profile": {
"assign_homes": [
23877,
80
],
"civilization_flow_capacity": [
121854,
20
],
"civilization_flow_colonies": [
53021,
20
],
"civilization_flow_finish": [
137725,
20
],
"civilization_flow_ledger": [
883114,
20
],
"civilization_flow_plan": [
1733356,
20
],
"civilization_operator_build": [
62368,
20
],
"civilization_operator_solve": [
680005,
860
],
"enforce_capacity": [
319290,
80
],
"generation_products": [
192497,
40
],
"generation_withdrawal": [
548252,
60
],
"generations": [
2737148,
20
],
"initialize": [
115930,
20
],
"newborn_batches": [
121459,
3016
],
"physical_amounts_build": [
287621,
200
],
"production": [
376359,
20
],
"trends": [
359971,
20
]
},
"run": 1,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 387.438,
"mean_ms": 350.1367,
"min_ms": 298.254,
"model": "after",
"profile": {
"assign_homes": [
24181,
80
],
"civilization_flow_capacity": [
122008,
20
],
"civilization_flow_colonies": [
53315,
20
],
"civilization_flow_finish": [
137169,
20
],
"civilization_flow_ledger": [
885851,
20
],
"civilization_flow_plan": [
1736978,
20
],
"civilization_operator_build": [
62264,
20
],
"civilization_operator_solve": [
680485,
860
],
"enforce_capacity": [
319558,
80
],
"generation_products": [
193735,
40
],
"generation_withdrawal": [
547473,
60
],
"generations": [
2743162,
20
],
"initialize": [
115720,
20
],
"newborn_batches": [
121401,
3016
],
"physical_amounts_build": [
287832,
200
],
"production": [
376295,
20
],
"trends": [
360758,
20
]
},
"run": 2,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 391.136,
"mean_ms": 353.55755,
"min_ms": 295.752,
"model": "before",
"profile": {
"assign_homes": [
23999,
80
],
"civilization_flow_capacity": [
125136,
20
],
"civilization_flow_colonies": [
53681,
20
],
"civilization_flow_finish": [
137614,
20
],
"civilization_flow_ledger": [
893234,
20
],
"civilization_flow_plan": [
1749507,
20
],
"civilization_operator_build": [
63052,
20
],
"civilization_operator_solve": [
683642,
860
],
"enforce_capacity": [
319168,
80
],
"generation_products": [
193901,
40
],
"generation_withdrawal": [
550377,
60
],
"generations": [
2768999,
20
],
"initialize": [
115976,
20
],
"newborn_batches": [
122734,
3016
],
"physical_amounts_build": [
292064,
200
],
"production": [
382112,
20
],
"trends": [
363057,
20
]
},
"run": 2,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 418.952,
"mean_ms": 402.88125,
"min_ms": 362.784,
"model": "before",
"profile": {
"assign_homes": [
18182,
60
],
"civilization_flow_capacity": [
138911,
20
],
"civilization_flow_colonies": [
235353,
20
],
"civilization_flow_finish": [
139454,
20
],
"civilization_flow_ledger": [
1808094,
20
],
"civilization_flow_plan": [
1823967,
20
],
"civilization_operator_build": [
63710,
20
],
"civilization_operator_solve": [
684725,
860
],
"enforce_capacity": [
241433,
60
],
"generation_products": [
79394,
20
],
"generation_withdrawal": [
814774,
40
],
"generations": [
2390414,
20
],
"initialize": [
142892,
20
],
"newborn_batches": [
55241,
1490
],
"physical_amounts_build": [
359891,
140
],
"production": [
410388,
20
],
"trends": [
362721,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle"
},
{
"cycles": 20,
"max_ms": 425.202,
"mean_ms": 404.3497,
"min_ms": 355.885,
"model": "after",
"profile": {
"assign_homes": [
18056,
60
],
"civilization_flow_capacity": [
139522,
20
],
"civilization_flow_colonies": [
236599,
20
],
"civilization_flow_finish": [
140701,
20
],
"civilization_flow_ledger": [
1809113,
20
],
"civilization_flow_plan": [
1824567,
20
],
"civilization_operator_build": [
63452,
20
],
"civilization_operator_solve": [
684152,
860
],
"enforce_capacity": [
242790,
60
],
"generation_products": [
80500,
20
],
"generation_withdrawal": [
817704,
40
],
"generations": [
2406807,
20
],
"initialize": [
143427,
20
],
"newborn_batches": [
55885,
1490
],
"physical_amounts_build": [
363175,
140
],
"production": [
412428,
20
],
"trends": [
365215,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle"
},
{
"cycles": 20,
"max_ms": 418.858,
"mean_ms": 404.4306,
"min_ms": 357.106,
"model": "after",
"profile": {
"assign_homes": [
18133,
60
],
"civilization_flow_capacity": [
139021,
20
],
"civilization_flow_colonies": [
237100,
20
],
"civilization_flow_finish": [
139480,
20
],
"civilization_flow_ledger": [
1815717,
20
],
"civilization_flow_plan": [
1825515,
20
],
"civilization_operator_build": [
63298,
20
],
"civilization_operator_solve": [
685950,
860
],
"enforce_capacity": [
242932,
60
],
"generation_products": [
79477,
20
],
"generation_withdrawal": [
815781,
40
],
"generations": [
2400821,
20
],
"initialize": [
144101,
20
],
"newborn_batches": [
56140,
1490
],
"physical_amounts_build": [
362394,
140
],
"production": [
412986,
20
],
"trends": [
364511,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle"
},
{
"cycles": 20,
"max_ms": 422.492,
"mean_ms": 405.6284,
"min_ms": 355.699,
"model": "before",
"profile": {
"assign_homes": [
18208,
60
],
"civilization_flow_capacity": [
139305,
20
],
"civilization_flow_colonies": [
236973,
20
],
"civilization_flow_finish": [
141048,
20
],
"civilization_flow_ledger": [
1818465,
20
],
"civilization_flow_plan": [
1832211,
20
],
"civilization_operator_build": [
63519,
20
],
"civilization_operator_solve": [
688184,
860
],
"enforce_capacity": [
243601,
60
],
"generation_products": [
79839,
20
],
"generation_withdrawal": [
822003,
40
],
"generations": [
2410757,
20
],
"initialize": [
144424,
20
],
"newborn_batches": [
55500,
1490
],
"physical_amounts_build": [
363454,
140
],
"production": [
412711,
20
],
"trends": [
367008,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle"
}
],
"speed": 6
}
@@ -0,0 +1,645 @@
{
"reference_commit": "c429403",
"delta_seconds": 0.2,
"exact_state_differential": true,
"input_sha256": {
"all_objects_speed6": "44bee3b3753fa51615f766d8f800a10cc2b70c8e7fb4ea12b6e18da7f07a7d67",
"many_objects_fragmented_stars_cycle": "1e95ef398923f26b912a305c84f9b3eb502d24cb14e8ae687a4becccf047324a"
},
"order": "before-after-after-before",
"runs": [
{
"cycles": 20,
"max_ms": 417.124,
"mean_ms": 381.20745,
"min_ms": 328.622,
"model": "before",
"profile": {
"assign_homes": [
24482,
80
],
"civilization_flow_capacity": [
127513,
20
],
"civilization_flow_colonies": [
54148,
20
],
"civilization_flow_finish": [
140918,
20
],
"civilization_flow_ledger": [
905665,
20
],
"civilization_flow_plan": [
2240897,
20
],
"civilization_operator_build": [
63936,
20
],
"civilization_operator_solve": [
685115,
860
],
"enforce_capacity": [
323853,
80
],
"generation_products": [
195550,
40
],
"generation_withdrawal": [
565054,
60
],
"generations": [
2802836,
20
],
"initialize": [
117049,
20
],
"newborn_batches": [
123941,
3016
],
"physical_amounts_build": [
294599,
200
],
"production": [
385593,
20
],
"trends": [
363466,
20
]
},
"run": 1,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 390.389,
"mean_ms": 357.03875,
"min_ms": 302.117,
"model": "after",
"profile": {
"assign_homes": [
24490,
80
],
"civilization_flow_capacity": [
125647,
20
],
"civilization_flow_colonies": [
54325,
20
],
"civilization_flow_finish": [
141339,
20
],
"civilization_flow_ledger": [
902399,
20
],
"civilization_flow_plan": [
1755403,
20
],
"civilization_operator_build": [
63055,
20
],
"civilization_operator_solve": [
683933,
860
],
"enforce_capacity": [
324284,
80
],
"generation_products": [
197296,
40
],
"generation_withdrawal": [
562515,
60
],
"generations": [
2804447,
20
],
"initialize": [
117904,
20
],
"newborn_batches": [
123892,
3016
],
"physical_amounts_build": [
296839,
200
],
"production": [
389361,
20
],
"trends": [
364246,
20
]
},
"run": 1,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 390.735,
"mean_ms": 357.0124,
"min_ms": 304.896,
"model": "after",
"profile": {
"assign_homes": [
24460,
80
],
"civilization_flow_capacity": [
125447,
20
],
"civilization_flow_colonies": [
54216,
20
],
"civilization_flow_finish": [
139778,
20
],
"civilization_flow_ledger": [
906760,
20
],
"civilization_flow_plan": [
1757858,
20
],
"civilization_operator_build": [
63411,
20
],
"civilization_operator_solve": [
687170,
860
],
"enforce_capacity": [
325360,
80
],
"generation_products": [
196450,
40
],
"generation_withdrawal": [
558924,
60
],
"generations": [
2799057,
20
],
"initialize": [
117825,
20
],
"newborn_batches": [
123788,
3016
],
"physical_amounts_build": [
295498,
200
],
"production": [
385114,
20
],
"trends": [
367268,
20
]
},
"run": 2,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 410.865,
"mean_ms": 379.9508,
"min_ms": 328.074,
"model": "before",
"profile": {
"assign_homes": [
24504,
80
],
"civilization_flow_capacity": [
125523,
20
],
"civilization_flow_colonies": [
54072,
20
],
"civilization_flow_finish": [
138884,
20
],
"civilization_flow_ledger": [
900719,
20
],
"civilization_flow_plan": [
2238292,
20
],
"civilization_operator_build": [
63477,
20
],
"civilization_operator_solve": [
687863,
860
],
"enforce_capacity": [
323929,
80
],
"generation_products": [
196146,
40
],
"generation_withdrawal": [
557728,
60
],
"generations": [
2793469,
20
],
"initialize": [
117168,
20
],
"newborn_batches": [
123830,
3016
],
"physical_amounts_build": [
294159,
200
],
"production": [
382811,
20
],
"trends": [
366676,
20
]
},
"run": 2,
"save": "all_objects_speed6"
},
{
"cycles": 20,
"max_ms": 454.17,
"mean_ms": 431.32255,
"min_ms": 380.165,
"model": "before",
"profile": {
"assign_homes": [
18277,
60
],
"civilization_flow_capacity": [
138886,
20
],
"civilization_flow_colonies": [
239194,
20
],
"civilization_flow_finish": [
140538,
20
],
"civilization_flow_ledger": [
1823769,
20
],
"civilization_flow_plan": [
2332907,
20
],
"civilization_operator_build": [
63920,
20
],
"civilization_operator_solve": [
689691,
860
],
"enforce_capacity": [
244580,
60
],
"generation_products": [
80081,
20
],
"generation_withdrawal": [
823351,
40
],
"generations": [
2417052,
20
],
"initialize": [
145160,
20
],
"newborn_batches": [
55841,
1490
],
"physical_amounts_build": [
364671,
140
],
"production": [
414028,
20
],
"trends": [
365376,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle"
},
{
"cycles": 20,
"max_ms": 437.315,
"mean_ms": 409.44945,
"min_ms": 360.224,
"model": "after",
"profile": {
"assign_homes": [
18338,
60
],
"civilization_flow_capacity": [
139951,
20
],
"civilization_flow_colonies": [
238906,
20
],
"civilization_flow_finish": [
141435,
20
],
"civilization_flow_ledger": [
1834486,
20
],
"civilization_flow_plan": [
1841420,
20
],
"civilization_operator_build": [
64315,
20
],
"civilization_operator_solve": [
691338,
860
],
"enforce_capacity": [
245861,
60
],
"generation_products": [
79738,
20
],
"generation_withdrawal": [
825947,
40
],
"generations": [
2423500,
20
],
"initialize": [
145491,
20
],
"newborn_batches": [
56085,
1490
],
"physical_amounts_build": [
371874,
140
],
"production": [
441338,
20
],
"trends": [
366919,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle"
},
{
"cycles": 20,
"max_ms": 424.959,
"mean_ms": 409.08685,
"min_ms": 359.281,
"model": "after",
"profile": {
"assign_homes": [
18404,
60
],
"civilization_flow_capacity": [
140108,
20
],
"civilization_flow_colonies": [
239591,
20
],
"civilization_flow_finish": [
141361,
20
],
"civilization_flow_ledger": [
1836691,
20
],
"civilization_flow_plan": [
1842577,
20
],
"civilization_operator_build": [
64001,
20
],
"civilization_operator_solve": [
692117,
860
],
"enforce_capacity": [
245631,
60
],
"generation_products": [
80486,
20
],
"generation_withdrawal": [
829783,
40
],
"generations": [
2438245,
20
],
"initialize": [
145228,
20
],
"newborn_batches": [
56248,
1490
],
"physical_amounts_build": [
369443,
140
],
"production": [
416359,
20
],
"trends": [
367683,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle"
},
{
"cycles": 20,
"max_ms": 452.639,
"mean_ms": 434.50095,
"min_ms": 387.05,
"model": "before",
"profile": {
"assign_homes": [
18271,
60
],
"civilization_flow_capacity": [
140778,
20
],
"civilization_flow_colonies": [
239800,
20
],
"civilization_flow_finish": [
141233,
20
],
"civilization_flow_ledger": [
1836252,
20
],
"civilization_flow_plan": [
2347237,
20
],
"civilization_operator_build": [
64454,
20
],
"civilization_operator_solve": [
693176,
860
],
"enforce_capacity": [
244735,
60
],
"generation_products": [
80563,
20
],
"generation_withdrawal": [
830888,
40
],
"generations": [
2436028,
20
],
"initialize": [
145261,
20
],
"newborn_batches": [
56321,
1490
],
"physical_amounts_build": [
369324,
140
],
"production": [
417541,
20
],
"trends": [
369444,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle"
}
],
"speed": 6
}
@@ -0,0 +1,172 @@
{
"cycles_per_run": 30,
"godot": {
"build": "official",
"hash": "ed1daf0bf001b61586d9930840f2f1394092c079",
"hex": 263938,
"major": 4,
"minor": 7,
"patch": 2,
"status": "stable",
"string": "4.7.2-stable (official)",
"timestamp": 0
},
"input_saves_unchanged": true,
"machine": "Mac16,8",
"repeats": 2,
"results": [
{
"applied_game_years": "600000",
"cadence_steps": {
"0.2": 30
},
"cpu_cycles_per_second": 5.5818824005294605,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 5.0,
"max_ms": 351.156,
"mean_ms_per_cycle": 179.15103333333334,
"mode": "fixed_5",
"p95_ms": 188.985,
"repeat": 0,
"requested_simulation_seconds": 6.000000000000003,
"save": "all_objects_speed6.adventure.json",
"simulated_seconds_per_cpu_second": 1.1163764801058926
},
{
"applied_game_years": "978333",
"cadence_steps": {
"0.2": 1,
"0.25": 1,
"0.33333333333333": 28
},
"cpu_cycles_per_second": 5.126867760649103,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 3.0,
"max_ms": 349.806,
"mean_ms_per_cycle": 195.05086666666668,
"mode": "adaptive_5_4_3",
"p95_ms": 214.441,
"repeat": 0,
"requested_simulation_seconds": 9.783333333333333,
"save": "all_objects_speed6.adventure.json",
"simulated_seconds_per_cpu_second": 1.671928541945013
},
{
"applied_game_years": "978333",
"cadence_steps": {
"0.2": 1,
"0.25": 1,
"0.33333333333333": 28
},
"cpu_cycles_per_second": 5.029407785556781,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 3.0,
"max_ms": 340.747,
"mean_ms_per_cycle": 198.83056666666667,
"mode": "adaptive_5_4_3",
"p95_ms": 233.775,
"repeat": 1,
"requested_simulation_seconds": 9.783333333333333,
"save": "all_objects_speed6.adventure.json",
"simulated_seconds_per_cpu_second": 1.6401457611787948
},
{
"applied_game_years": "600000",
"cadence_steps": {
"0.2": 30
},
"cpu_cycles_per_second": 5.403485464263869,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 5.0,
"max_ms": 351.698,
"mean_ms_per_cycle": 185.06573333333333,
"mode": "fixed_5",
"p95_ms": 195.396,
"repeat": 1,
"requested_simulation_seconds": 6.000000000000003,
"save": "all_objects_speed6.adventure.json",
"simulated_seconds_per_cpu_second": 1.0806970928527742
},
{
"applied_game_years": "600000",
"cadence_steps": {
"0.2": 30
},
"cpu_cycles_per_second": 5.218331511555821,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 5.0,
"max_ms": 373.477,
"mean_ms_per_cycle": 191.63213333333334,
"mode": "fixed_5",
"p95_ms": 250.5,
"repeat": 0,
"requested_simulation_seconds": 6.000000000000003,
"save": "many_objects_fragmented_stars_cycle.adventure.json",
"simulated_seconds_per_cpu_second": 1.0436663023111648
},
{
"applied_game_years": "978333",
"cadence_steps": {
"0.2": 1,
"0.25": 1,
"0.33333333333333": 28
},
"cpu_cycles_per_second": 5.21296979935453,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 3.0,
"max_ms": 373.168,
"mean_ms_per_cycle": 191.82923333333332,
"mode": "adaptive_5_4_3",
"p95_ms": 258.047,
"repeat": 0,
"requested_simulation_seconds": 9.783333333333333,
"save": "many_objects_fragmented_stars_cycle.adventure.json",
"simulated_seconds_per_cpu_second": 1.7000073734561718
},
{
"applied_game_years": "978333",
"cadence_steps": {
"0.2": 1,
"0.25": 1,
"0.33333333333333": 28
},
"cpu_cycles_per_second": 5.195109254879333,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 3.0,
"max_ms": 372.878,
"mean_ms_per_cycle": 192.48873333333333,
"mode": "adaptive_5_4_3",
"p95_ms": 254.848,
"repeat": 1,
"requested_simulation_seconds": 9.783333333333333,
"save": "many_objects_fragmented_stars_cycle.adventure.json",
"simulated_seconds_per_cpu_second": 1.694182851452316
},
{
"applied_game_years": "600000",
"cadence_steps": {
"0.2": 30
},
"cpu_cycles_per_second": 5.137478056546851,
"cycles": 30,
"final_adaptive_quality": 3,
"final_cadence_hz": 5.0,
"max_ms": 375.784,
"mean_ms_per_cycle": 194.64803333333333,
"mode": "fixed_5",
"p95_ms": 255.446,
"repeat": 1,
"requested_simulation_seconds": 6.000000000000003,
"save": "many_objects_fragmented_stars_cycle.adventure.json",
"simulated_seconds_per_cpu_second": 1.0274956113093707
}
],
"speed": 6
}
@@ -0,0 +1,359 @@
{
"godot": {
"build": "official",
"hash": "ed1daf0bf001b61586d9930840f2f1394092c079",
"hex": 263938,
"major": 4,
"minor": 7,
"patch": 2,
"status": "stable",
"string": "4.7.2-stable (official)",
"timestamp": 0
},
"machine": "Mac16,8",
"results": [
{
"commits": 48,
"completed_cycles_per_second": 4.743091908967023,
"cpu_profile_usec_calls": {
"assign_homes": [
51095,
170
],
"civilization_flow_capacity": [
111005,
48
],
"civilization_flow_colonies": [
131724,
48
],
"civilization_flow_finish": [
325933,
48
],
"civilization_flow_ledger": [
928271,
48
],
"civilization_flow_plan": [
825706,
48
],
"civilization_operator_build": [
148485,
48
],
"civilization_operator_solve": [
104924,
2064
],
"enforce_capacity": [
626179,
170
],
"generation_products": [
235626,
74
],
"generation_withdrawal": [
455348,
97
],
"generations": [
3654469,
48
],
"initialize": [
215278,
48
],
"newborn_batches": [
237070,
3088
],
"physical_amounts_build": [
630353,
437
],
"production": [
1115470,
48
],
"trends": [
865132,
48
]
},
"elapsed_seconds": 10.119981,
"final_adaptive_quality": 3,
"final_cadence_hz": 5.0,
"frames": 246,
"mean_render_fps": 24.584772192104204,
"mode": "fixed_5",
"published_to_requested": 0.9943600000000001,
"published_years": "1006667",
"render_max_ms": 237.132,
"render_p95_ms": 111.449,
"requested_years": "1012372"
},
{
"commits": 21,
"completed_cycles_per_second": 2.085573876935847,
"cpu_profile_usec_calls": {
"assign_homes": [
30276,
75
],
"civilization_flow_capacity": [
51694,
21
],
"civilization_flow_colonies": [
52773,
21
],
"civilization_flow_finish": [
142952,
21
],
"civilization_flow_ledger": [
309711,
21
],
"civilization_flow_plan": [
435139,
21
],
"civilization_operator_build": [
74385,
21
],
"civilization_operator_solve": [
87284,
903
],
"enforce_capacity": [
414865,
75
],
"generation_products": [
105449,
33
],
"generation_withdrawal": [
152279,
43
],
"generations": [
1503309,
21
],
"initialize": [
255626,
21
],
"newborn_batches": [
114834,
1633
],
"physical_amounts_build": [
274341,
193
],
"production": [
685165,
21
],
"trends": [
377582,
21
]
},
"elapsed_seconds": 10.069171,
"final_adaptive_quality": 3,
"final_cadence_hz": 2.0,
"frames": 879,
"mean_render_fps": 87.80117977216634,
"mode": "adaptive_5_4_3_2",
"published_to_requested": 0.9889800000000001,
"published_years": "989844",
"render_max_ms": 203.26,
"render_p95_ms": 42.712,
"requested_years": "1000874"
},
{
"commits": 21,
"completed_cycles_per_second": 2.0775335521668676,
"cpu_profile_usec_calls": {
"assign_homes": [
31004,
76
],
"civilization_flow_capacity": [
51172,
21
],
"civilization_flow_colonies": [
52782,
21
],
"civilization_flow_finish": [
142851,
21
],
"civilization_flow_ledger": [
309607,
21
],
"civilization_flow_plan": [
431321,
21
],
"civilization_operator_build": [
74854,
21
],
"civilization_operator_solve": [
86348,
903
],
"enforce_capacity": [
423638,
76
],
"generation_products": [
105537,
34
],
"generation_withdrawal": [
151611,
43
],
"generations": [
1516916,
21
],
"initialize": [
261806,
21
],
"newborn_batches": [
112492,
1621
],
"physical_amounts_build": [
278871,
195
],
"production": [
696352,
21
],
"trends": [
377969,
21
]
},
"elapsed_seconds": 10.10814,
"final_adaptive_quality": 3,
"final_cadence_hz": 2.0,
"frames": 880,
"mean_render_fps": 88.00051040296032,
"mode": "adaptive_5_4_3_2",
"published_to_requested": 0.9933700000000001,
"published_years": "994002",
"render_max_ms": 201.104,
"render_p95_ms": 42.207,
"requested_years": "1000640"
},
{
"commits": 47,
"completed_cycles_per_second": 4.646829581875308,
"cpu_profile_usec_calls": {
"assign_homes": [
50139,
165
],
"civilization_flow_capacity": [
108674,
47
],
"civilization_flow_colonies": [
129977,
47
],
"civilization_flow_finish": [
323458,
47
],
"civilization_flow_ledger": [
911542,
47
],
"civilization_flow_plan": [
807459,
47
],
"civilization_operator_build": [
147856,
47
],
"civilization_operator_solve": [
96154,
2021
],
"enforce_capacity": [
619397,
165
],
"generation_products": [
234744,
71
],
"generation_withdrawal": [
451797,
95
],
"generations": [
3665554,
47
],
"initialize": [
217827,
47
],
"newborn_batches": [
265766,
3178
],
"physical_amounts_build": [
623211,
428
],
"production": [
1142731,
47
],
"trends": [
856364,
47
]
},
"elapsed_seconds": 10.114423,
"final_adaptive_quality": 3,
"final_cadence_hz": 5.0,
"frames": 239,
"mean_render_fps": 23.87480014844331,
"mode": "fixed_5",
"published_to_requested": 0.9904200000000001,
"published_years": "992039",
"render_max_ms": 201.438,
"render_p95_ms": 114.211,
"requested_years": "1001635"
}
],
"save": "all_objects_speed6.adventure.json",
"speed": 6,
"wall_seconds_per_run": 10.0
}
@@ -0,0 +1,763 @@
{
"delta_seconds": 0.2,
"input_sha256": {
"all_objects_speed6": "44bee3b3753fa51615f766d8f800a10cc2b70c8e7fb4ea12b6e18da7f07a7d67",
"many_objects_fragmented_stars_cycle": "1e95ef398923f26b912a305c84f9b3eb502d24cb14e8ae687a4becccf047324a"
},
"order": "before-after-after-before",
"profile_enabled": true,
"runs": [
{
"cycles": 20,
"max_ms": 407.447,
"mean_ms": 382.1863,
"min_ms": 338.878,
"model": "before",
"profile": {
"assign_homes": [
23607,
80
],
"civilization_flow_capacity": [
119827,
20
],
"civilization_flow_colonies": [
100257,
20
],
"civilization_flow_finish": [
167710,
20
],
"civilization_flow_ledger": [
1031564,
20
],
"civilization_flow_plan": [
2148765,
20
],
"civilization_operator_build": [
60291,
20
],
"civilization_operator_solve": [
665230,
860
],
"enforce_capacity": [
338749,
80
],
"generation_products": [
187536,
40
],
"generation_withdrawal": [
520136,
60
],
"generations": [
2614358,
20
],
"initialize": [
112213,
20
],
"newborn_batches": [
118460,
3016
],
"physical_amounts_build": [
478063,
360
],
"production": [
537724,
20
],
"trends": [
349106,
20
]
},
"run": 1,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 406.895,
"mean_ms": 376.23395,
"min_ms": 324.14,
"model": "after",
"profile": {
"assign_homes": [
23735,
80
],
"civilization_flow_capacity": [
120538,
20
],
"civilization_flow_colonies": [
50071,
20
],
"civilization_flow_finish": [
169132,
20
],
"civilization_flow_ledger": [
892018,
20
],
"civilization_flow_plan": [
2165803,
20
],
"civilization_operator_build": [
60679,
20
],
"civilization_operator_solve": [
670541,
860
],
"enforce_capacity": [
340289,
80
],
"generation_products": [
188663,
40
],
"generation_withdrawal": [
534195,
60
],
"generations": [
2649493,
20
],
"initialize": [
113482,
20
],
"newborn_batches": [
118902,
3016
],
"physical_amounts_build": [
489793,
360
],
"production": [
548119,
20
],
"trends": [
350991,
20
]
},
"run": 1,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 413.636,
"mean_ms": 378.2097,
"min_ms": 326.044,
"model": "after",
"profile": {
"assign_homes": [
23901,
80
],
"civilization_flow_capacity": [
121116,
20
],
"civilization_flow_colonies": [
50174,
20
],
"civilization_flow_finish": [
170232,
20
],
"civilization_flow_ledger": [
899208,
20
],
"civilization_flow_plan": [
2173515,
20
],
"civilization_operator_build": [
61133,
20
],
"civilization_operator_solve": [
672190,
860
],
"enforce_capacity": [
341889,
80
],
"generation_products": [
190021,
40
],
"generation_withdrawal": [
537089,
60
],
"generations": [
2668071,
20
],
"initialize": [
113520,
20
],
"newborn_batches": [
120186,
3016
],
"physical_amounts_build": [
491086,
360
],
"production": [
549548,
20
],
"trends": [
352056,
20
]
},
"run": 2,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 410.851,
"mean_ms": 388.3392,
"min_ms": 346.936,
"model": "before",
"profile": {
"assign_homes": [
23780,
80
],
"civilization_flow_capacity": [
121258,
20
],
"civilization_flow_colonies": [
101386,
20
],
"civilization_flow_finish": [
171098,
20
],
"civilization_flow_ledger": [
1048869,
20
],
"civilization_flow_plan": [
2182234,
20
],
"civilization_operator_build": [
61215,
20
],
"civilization_operator_solve": [
676192,
860
],
"enforce_capacity": [
343586,
80
],
"generation_products": [
191018,
40
],
"generation_withdrawal": [
528628,
60
],
"generations": [
2654320,
20
],
"initialize": [
113911,
20
],
"newborn_batches": [
120518,
3016
],
"physical_amounts_build": [
485368,
360
],
"production": [
550201,
20
],
"trends": [
354035,
20
]
},
"run": 2,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 540.275,
"mean_ms": 504.8664,
"min_ms": 445.592,
"model": "before",
"profile": {
"assign_homes": [
17920,
60
],
"civilization_flow_capacity": [
136151,
20
],
"civilization_flow_colonies": [
1064307,
20
],
"civilization_flow_finish": [
195983,
20
],
"civilization_flow_ledger": [
2251097,
20
],
"civilization_flow_plan": [
2284423,
20
],
"civilization_operator_build": [
62124,
20
],
"civilization_operator_solve": [
678656,
860
],
"enforce_capacity": [
288140,
60
],
"generation_products": [
77749,
20
],
"generation_withdrawal": [
795116,
40
],
"generations": [
2321187,
20
],
"initialize": [
140958,
20
],
"newborn_batches": [
54878,
1490
],
"physical_amounts_build": [
746203,
300
],
"production": [
744868,
20
],
"trends": [
355994,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 462.134,
"mean_ms": 441.9819,
"min_ms": 389.761,
"model": "after",
"profile": {
"assign_homes": [
17865,
60
],
"civilization_flow_capacity": [
137136,
20
],
"civilization_flow_colonies": [
229748,
20
],
"civilization_flow_finish": [
196611,
20
],
"civilization_flow_ledger": [
1828223,
20
],
"civilization_flow_plan": [
2286989,
20
],
"civilization_operator_build": [
62177,
20
],
"civilization_operator_solve": [
678566,
860
],
"enforce_capacity": [
288805,
60
],
"generation_products": [
78203,
20
],
"generation_withdrawal": [
805143,
40
],
"generations": [
2312257,
20
],
"initialize": [
141951,
20
],
"newborn_batches": [
55057,
1490
],
"physical_amounts_build": [
746690,
300
],
"production": [
749636,
20
],
"trends": [
355174,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 467.425,
"mean_ms": 444.6246,
"min_ms": 389.196,
"model": "after",
"profile": {
"assign_homes": [
18039,
60
],
"civilization_flow_capacity": [
137286,
20
],
"civilization_flow_colonies": [
231059,
20
],
"civilization_flow_finish": [
197868,
20
],
"civilization_flow_ledger": [
1835420,
20
],
"civilization_flow_plan": [
2302705,
20
],
"civilization_operator_build": [
62638,
20
],
"civilization_operator_solve": [
680863,
860
],
"enforce_capacity": [
289856,
60
],
"generation_products": [
78541,
20
],
"generation_withdrawal": [
808144,
40
],
"generations": [
2327718,
20
],
"initialize": [
142049,
20
],
"newborn_batches": [
55184,
1490
],
"physical_amounts_build": [
752516,
300
],
"production": [
752648,
20
],
"trends": [
358218,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 532.382,
"mean_ms": 508.9093,
"min_ms": 451.042,
"model": "before",
"profile": {
"assign_homes": [
18208,
60
],
"civilization_flow_capacity": [
137370,
20
],
"civilization_flow_colonies": [
1075170,
20
],
"civilization_flow_finish": [
198272,
20
],
"civilization_flow_ledger": [
2269355,
20
],
"civilization_flow_plan": [
2307886,
20
],
"civilization_operator_build": [
62940,
20
],
"civilization_operator_solve": [
683671,
860
],
"enforce_capacity": [
291171,
60
],
"generation_products": [
78498,
20
],
"generation_withdrawal": [
807187,
40
],
"generations": [
2322215,
20
],
"initialize": [
143015,
20
],
"newborn_batches": [
55362,
1490
],
"physical_amounts_build": [
749843,
300
],
"production": [
753597,
20
],
"trends": [
360333,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
}
],
"speed": 6,
"reference_commit": "413f2be9044090c878c2909dd9d0646f77513a5f",
"reference_script_sha256": "b2a04cc50ad1e77b45762264846cb546b013b09f4c204ba22e6472a38728e5b7",
"candidate_script_sha256": "cbc65eaf4ab0e67fb9d6d5d7ad11757bdeb5f0b1e0ffb2f2631bfe3dcd9e962e",
"summary": {
"all_objects_speed6": {
"before": {
"mean_ms": 385.26275,
"cycles_per_second": 2.5956311634073113,
"profile_ms": {
"assign_homes": 1.184675,
"civilization_flow_capacity": 6.027125,
"civilization_flow_colonies": 5.041075,
"civilization_flow_finish": 8.4702,
"civilization_flow_ledger": 52.010825,
"civilization_flow_plan": 108.27497500000001,
"civilization_operator_build": 3.03765,
"civilization_operator_solve": 33.53555,
"enforce_capacity": 17.058375,
"generation_products": 9.46385,
"generation_withdrawal": 26.219099999999997,
"generations": 131.71695000000003,
"initialize": 5.6531,
"newborn_batches": 5.97445,
"physical_amounts_build": 24.085775,
"production": 27.198125,
"trends": 17.578525000000003
}
},
"after": {
"mean_ms": 377.22182499999997,
"cycles_per_second": 2.6509600816442687,
"profile_ms": {
"assign_homes": 1.1909,
"civilization_flow_capacity": 6.04135,
"civilization_flow_colonies": 2.506125,
"civilization_flow_finish": 8.4841,
"civilization_flow_ledger": 44.78065,
"civilization_flow_plan": 108.48295,
"civilization_operator_build": 3.0453,
"civilization_operator_solve": 33.568275,
"enforce_capacity": 17.05445,
"generation_products": 9.4671,
"generation_withdrawal": 26.7821,
"generations": 132.9391,
"initialize": 5.675050000000001,
"newborn_batches": 5.9772,
"physical_amounts_build": 24.521974999999998,
"production": 27.441675,
"trends": 17.576175
}
},
"reduction_percent": 2.0871275512621024
},
"many_objects_fragmented_stars_cycle": {
"before": {
"mean_ms": 506.88784999999996,
"cycles_per_second": 1.9728229824407906,
"profile_ms": {
"assign_homes": 0.9032,
"civilization_flow_capacity": 6.838025,
"civilization_flow_colonies": 53.486925,
"civilization_flow_finish": 9.856375,
"civilization_flow_ledger": 113.0113,
"civilization_flow_plan": 114.807725,
"civilization_operator_build": 3.1266,
"civilization_operator_solve": 34.058175000000006,
"enforce_capacity": 14.482775,
"generation_products": 3.906175,
"generation_withdrawal": 40.057575,
"generations": 116.08505000000001,
"initialize": 7.099324999999999,
"newborn_batches": 2.756,
"physical_amounts_build": 37.40115,
"production": 37.461625,
"trends": 17.908175
}
},
"after": {
"mean_ms": 443.30325,
"cycles_per_second": 2.255792169355853,
"profile_ms": {
"assign_homes": 0.8976000000000001,
"civilization_flow_capacity": 6.86055,
"civilization_flow_colonies": 11.520175,
"civilization_flow_finish": 9.861975000000001,
"civilization_flow_ledger": 91.591075,
"civilization_flow_plan": 114.74235,
"civilization_operator_build": 3.120375,
"civilization_operator_solve": 33.985724999999995,
"enforce_capacity": 14.466524999999999,
"generation_products": 3.9186,
"generation_withdrawal": 40.332175,
"generations": 115.999375,
"initialize": 7.1,
"newborn_batches": 2.756025,
"physical_amounts_build": 37.48015,
"production": 37.5571,
"trends": 17.834799999999998
}
},
"reduction_percent": 12.544116020930463
}
}
}
@@ -0,0 +1,870 @@
{
"delta_seconds": 0.2,
"input_sha256": {
"all_objects_speed6": "44bee3b3753fa51615f766d8f800a10cc2b70c8e7fb4ea12b6e18da7f07a7d67",
"many_objects_fragmented_stars_cycle": "1e95ef398923f26b912a305c84f9b3eb502d24cb14e8ae687a4becccf047324a"
},
"order": "before-after-after-before",
"profile_enabled": true,
"runs": [
{
"cycles": 20,
"max_ms": 409.924,
"mean_ms": 374.42165,
"min_ms": 321.955,
"model": "before",
"profile": {
"assign_homes": [
23286,
80
],
"civilization_flow_capacity": [
120325,
20
],
"civilization_flow_colonies": [
49609,
20
],
"civilization_flow_finish": [
167957,
20
],
"civilization_flow_ledger": [
890864,
20
],
"civilization_flow_plan": [
2156571,
20
],
"civilization_operator_build": [
59939,
20
],
"civilization_operator_solve": [
667995,
860
],
"enforce_capacity": [
338483,
80
],
"generation_products": [
186561,
40
],
"generation_withdrawal": [
530120,
60
],
"generations": [
2635468,
20
],
"initialize": [
112680,
20
],
"newborn_batches": [
118343,
3016
],
"physical_amounts_build": [
488368,
360
],
"production": [
543976,
20
],
"trends": [
348509,
20
]
},
"run": 1,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 396.498,
"mean_ms": 366.8874,
"min_ms": 316.948,
"model": "after",
"profile": {
"assign_homes": [
23679,
80
],
"civilization_flow_capacity": [
121355,
20
],
"civilization_flow_colonies": [
52357,
20
],
"civilization_flow_finish": [
134122,
20
],
"civilization_flow_ledger": [
867762,
20
],
"civilization_flow_plan": [
2171014,
20
],
"civilization_operator_build": [
60589,
20
],
"civilization_operator_solve": [
672927,
860
],
"enforce_capacity": [
313233,
80
],
"generation_products": [
188758,
40
],
"generation_withdrawal": [
537217,
60
],
"generations": [
2691969,
20
],
"initialize": [
113057,
20
],
"newborn_batches": [
119560,
3016
],
"physical_amounts_build": [
283727,
200
],
"production": [
371874,
20
],
"trends": [
352124,
20
]
},
"run": 1,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 400.433,
"mean_ms": 367.76455,
"min_ms": 317.346,
"model": "after",
"profile": {
"assign_homes": [
23679,
80
],
"civilization_flow_capacity": [
120585,
20
],
"civilization_flow_colonies": [
52508,
20
],
"civilization_flow_finish": [
133714,
20
],
"civilization_flow_ledger": [
873014,
20
],
"civilization_flow_plan": [
2178049,
20
],
"civilization_operator_build": [
60872,
20
],
"civilization_operator_solve": [
674461,
860
],
"enforce_capacity": [
312963,
80
],
"generation_products": [
189817,
40
],
"generation_withdrawal": [
538355,
60
],
"generations": [
2697406,
20
],
"initialize": [
113617,
20
],
"newborn_batches": [
119833,
3016
],
"physical_amounts_build": [
283294,
200
],
"production": [
370613,
20
],
"trends": [
354430,
20
]
},
"run": 2,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 412.947,
"mean_ms": 379.94895,
"min_ms": 327.79,
"model": "before",
"profile": {
"assign_homes": [
23817,
80
],
"civilization_flow_capacity": [
120810,
20
],
"civilization_flow_colonies": [
50245,
20
],
"civilization_flow_finish": [
170562,
20
],
"civilization_flow_ledger": [
901208,
20
],
"civilization_flow_plan": [
2186592,
20
],
"civilization_operator_build": [
61551,
20
],
"civilization_operator_solve": [
676571,
860
],
"enforce_capacity": [
342898,
80
],
"generation_products": [
190410,
40
],
"generation_withdrawal": [
538869,
60
],
"generations": [
2675372,
20
],
"initialize": [
114216,
20
],
"newborn_batches": [
120558,
3016
],
"physical_amounts_build": [
495969,
360
],
"production": [
554663,
20
],
"trends": [
353982,
20
]
},
"run": 2,
"save": "all_objects_speed6",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 463.763,
"mean_ms": 441.44965,
"min_ms": 386.031,
"model": "before",
"profile": {
"assign_homes": [
17806,
60
],
"civilization_flow_capacity": [
136652,
20
],
"civilization_flow_colonies": [
230107,
20
],
"civilization_flow_finish": [
196342,
20
],
"civilization_flow_ledger": [
1825154,
20
],
"civilization_flow_plan": [
2292865,
20
],
"civilization_operator_build": [
62146,
20
],
"civilization_operator_solve": [
680101,
860
],
"enforce_capacity": [
288185,
60
],
"generation_products": [
77590,
20
],
"generation_withdrawal": [
800537,
40
],
"generations": [
2298719,
20
],
"initialize": [
141855,
20
],
"newborn_batches": [
54592,
1490
],
"physical_amounts_build": [
745858,
300
],
"production": [
747261,
20
],
"trends": [
356732,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 445.055,
"mean_ms": 423.0315,
"min_ms": 374.104,
"model": "after",
"profile": {
"assign_homes": [
17840,
60
],
"civilization_flow_capacity": [
137440,
20
],
"civilization_flow_colonies": [
231537,
20
],
"civilization_flow_finish": [
137928,
20
],
"civilization_flow_ledger": [
1782223,
20
],
"civilization_flow_plan": [
2310391,
20
],
"civilization_operator_build": [
61962,
20
],
"civilization_operator_solve": [
687903,
860
],
"enforce_capacity": [
238360,
60
],
"generation_products": [
77666,
20
],
"generation_withdrawal": [
802637,
40
],
"generations": [
2360346,
20
],
"initialize": [
141317,
20
],
"newborn_batches": [
54930,
1490
],
"physical_amounts_build": [
356207,
140
],
"production": [
405364,
20
],
"trends": [
357407,
20
]
},
"run": 1,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 441.891,
"mean_ms": 422.9136,
"min_ms": 376.819,
"model": "after",
"profile": {
"assign_homes": [
17821,
60
],
"civilization_flow_capacity": [
136949,
20
],
"civilization_flow_colonies": [
231992,
20
],
"civilization_flow_finish": [
137312,
20
],
"civilization_flow_ledger": [
1788156,
20
],
"civilization_flow_plan": [
2297486,
20
],
"civilization_operator_build": [
61993,
20
],
"civilization_operator_solve": [
683317,
860
],
"enforce_capacity": [
238430,
60
],
"generation_products": [
78170,
20
],
"generation_withdrawal": [
804912,
40
],
"generations": [
2364256,
20
],
"initialize": [
141451,
20
],
"newborn_batches": [
54900,
1490
],
"physical_amounts_build": [
354476,
140
],
"production": [
407287,
20
],
"trends": [
358298,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
},
{
"cycles": 20,
"max_ms": 465.049,
"mean_ms": 444.87595,
"min_ms": 392.884,
"model": "before",
"profile": {
"assign_homes": [
17906,
60
],
"civilization_flow_capacity": [
136963,
20
],
"civilization_flow_colonies": [
229784,
20
],
"civilization_flow_finish": [
196495,
20
],
"civilization_flow_ledger": [
1839904,
20
],
"civilization_flow_plan": [
2302987,
20
],
"civilization_operator_build": [
62518,
20
],
"civilization_operator_solve": [
683660,
860
],
"enforce_capacity": [
289846,
60
],
"generation_products": [
78882,
20
],
"generation_withdrawal": [
808021,
40
],
"generations": [
2328372,
20
],
"initialize": [
142755,
20
],
"newborn_batches": [
54963,
1490
],
"physical_amounts_build": [
754056,
300
],
"production": [
754898,
20
],
"trends": [
358634,
20
]
},
"run": 2,
"save": "many_objects_fragmented_stars_cycle",
"validation": "",
"years": 400000
}
],
"speed": 6,
"reference_commit": "9357f757a95db96274d4e115c7e498b58eca6902",
"exact_differential": {
"cycles": 10,
"inputs": [
"all_objects_speed6",
"many_objects_fragmented_stars_cycle"
],
"delta_seconds": [
0.2,
0.53,
0.31,
0.2,
0.2
],
"equal_full_states": true
},
"checks": {
"flow_commit": 1971,
"civilization_flows": 1895,
"civilization_state": 70,
"frame_evolution": 38,
"production_transaction": 14,
"stellar_rules": 333
},
"summary": {
"all_objects_speed6": {
"before": {
"mean_ms": 377.1853,
"cycles_per_second": 2.651216789201488,
"profile_ms": {
"assign_homes": 1.177575,
"civilization_flow_capacity": 6.028375,
"civilization_flow_colonies": 2.49635,
"civilization_flow_finish": 8.462975,
"civilization_flow_ledger": 44.8018,
"civilization_flow_plan": 108.579075,
"civilization_operator_build": 3.03725,
"civilization_operator_solve": 33.61415,
"enforce_capacity": 17.034525000000002,
"generation_products": 9.424275,
"generation_withdrawal": 26.724725,
"generations": 132.771,
"initialize": 5.6724,
"newborn_batches": 5.972524999999999,
"physical_amounts_build": 24.608425,
"production": 27.465975,
"trends": 17.562275000000003
},
"profile_calls_per_cycle": {
"assign_homes": 4.0,
"civilization_flow_capacity": 1.0,
"civilization_flow_colonies": 1.0,
"civilization_flow_finish": 1.0,
"civilization_flow_ledger": 1.0,
"civilization_flow_plan": 1.0,
"civilization_operator_build": 1.0,
"civilization_operator_solve": 43.0,
"enforce_capacity": 4.0,
"generation_products": 2.0,
"generation_withdrawal": 3.0,
"generations": 1.0,
"initialize": 1.0,
"newborn_batches": 150.8,
"physical_amounts_build": 18.0,
"production": 1.0,
"trends": 1.0
}
},
"after": {
"mean_ms": 367.32597499999997,
"cycles_per_second": 2.7223775830173733,
"profile_ms": {
"assign_homes": 1.18395,
"civilization_flow_capacity": 6.0485,
"civilization_flow_colonies": 2.621625,
"civilization_flow_finish": 6.6959,
"civilization_flow_ledger": 43.519400000000005,
"civilization_flow_plan": 108.726575,
"civilization_operator_build": 3.036525,
"civilization_operator_solve": 33.6847,
"enforce_capacity": 15.6549,
"generation_products": 9.464375,
"generation_withdrawal": 26.8893,
"generations": 134.734375,
"initialize": 5.66685,
"newborn_batches": 5.984825,
"physical_amounts_build": 14.175525,
"production": 18.562175,
"trends": 17.66385
},
"profile_calls_per_cycle": {
"assign_homes": 4.0,
"civilization_flow_capacity": 1.0,
"civilization_flow_colonies": 1.0,
"civilization_flow_finish": 1.0,
"civilization_flow_ledger": 1.0,
"civilization_flow_plan": 1.0,
"civilization_operator_build": 1.0,
"civilization_operator_solve": 43.0,
"enforce_capacity": 4.0,
"generation_products": 2.0,
"generation_withdrawal": 3.0,
"generations": 1.0,
"initialize": 1.0,
"newborn_batches": 150.8,
"physical_amounts_build": 10.0,
"production": 1.0,
"trends": 1.0
}
},
"reduction_percent": 2.613920797019398
},
"many_objects_fragmented_stars_cycle": {
"before": {
"mean_ms": 443.1628,
"cycles_per_second": 2.2565070894939736,
"profile_ms": {
"assign_homes": 0.8927999999999999,
"civilization_flow_capacity": 6.840375,
"civilization_flow_colonies": 11.497275,
"civilization_flow_finish": 9.820924999999999,
"civilization_flow_ledger": 91.62644999999999,
"civilization_flow_plan": 114.8963,
"civilization_operator_build": 3.1166,
"civilization_operator_solve": 34.094025,
"enforce_capacity": 14.450775,
"generation_products": 3.9118000000000004,
"generation_withdrawal": 40.21395,
"generations": 115.677275,
"initialize": 7.11525,
"newborn_batches": 2.738875,
"physical_amounts_build": 37.49785,
"production": 37.553975,
"trends": 17.88415
},
"profile_calls_per_cycle": {
"assign_homes": 3.0,
"civilization_flow_capacity": 1.0,
"civilization_flow_colonies": 1.0,
"civilization_flow_finish": 1.0,
"civilization_flow_ledger": 1.0,
"civilization_flow_plan": 1.0,
"civilization_operator_build": 1.0,
"civilization_operator_solve": 43.0,
"enforce_capacity": 3.0,
"generation_products": 1.0,
"generation_withdrawal": 2.0,
"generations": 1.0,
"initialize": 1.0,
"newborn_batches": 74.5,
"physical_amounts_build": 15.0,
"production": 1.0,
"trends": 1.0
}
},
"after": {
"mean_ms": 422.97254999999996,
"cycles_per_second": 2.364219616615783,
"profile_ms": {
"assign_homes": 0.891525,
"civilization_flow_capacity": 6.859725,
"civilization_flow_colonies": 11.588225,
"civilization_flow_finish": 6.881,
"civilization_flow_ledger": 89.25947500000001,
"civilization_flow_plan": 115.19692500000001,
"civilization_operator_build": 3.098875,
"civilization_operator_solve": 34.2805,
"enforce_capacity": 11.91975,
"generation_products": 3.8959,
"generation_withdrawal": 40.188725,
"generations": 118.11505,
"initialize": 7.0691999999999995,
"newborn_batches": 2.74575,
"physical_amounts_build": 17.767075000000002,
"production": 20.316275,
"trends": 17.892625
},
"profile_calls_per_cycle": {
"assign_homes": 3.0,
"civilization_flow_capacity": 1.0,
"civilization_flow_colonies": 1.0,
"civilization_flow_finish": 1.0,
"civilization_flow_ledger": 1.0,
"civilization_flow_plan": 1.0,
"civilization_operator_build": 1.0,
"civilization_operator_solve": 43.0,
"enforce_capacity": 3.0,
"generation_products": 1.0,
"generation_withdrawal": 2.0,
"generations": 1.0,
"initialize": 1.0,
"newborn_batches": 74.5,
"physical_amounts_build": 7.0,
"production": 1.0,
"trends": 1.0
}
},
"reduction_percent": 4.5559442263655825
}
},
"candidate_script_sha256": {
"civilization_flow.gd": "5d2b5f5a90fc9182ac1f933d473441457be28b47800a0081b1409614dd28aab5",
"civilization_state.gd": "ae0832531c7ce244e6939793007b3d7be5bd32246be6c209a75fdb5e3e6ffe7a",
"civilization_evolution.gd": "24efc14eedee5e8a28422262e37e6c54e1a0c403a164bd8f4c96d749f89ac61f",
"evolution_coordinator.gd": "0b8cec7e53f047698545e9b97c986b99e0ea5bfc61a6b0177335aedb442e6048",
"object_ledger.gd": "f7fbfeb4be905b525ad91648167ed1e32c4d3b77553549c69c3ebb808ac99b4b",
"manual_production.gd": "138a94d3bb7d63470f2ca2eb841badef910ec27f3e247c5c9ed8e925c0971fa4",
"automatic_production.gd": "dba9f98cbecc90ad9935653c39f7fcfad53edb3ca2670bbce4e1594b58b399a5",
"universe_environment.gd": "e31c87b38a4ec0052f3d43f7be2af51ee7612a48fadcf0b5072ba678107e576e"
}
}
+6 -2
View File
@@ -18,7 +18,7 @@ func restore_json_integers(value):
func run():
var args = OS.get_cmdline_user_args()
if args.is_empty():
print("Usage: --script tests/benchmark_civilization_flows.gd -- /path/to/save.adventure.json [delta_seconds] [profile] [cycles=100]")
print("Usage: --script tests/benchmark_civilization_flows.gd -- /path/to/save.adventure.json [delta_seconds] [profile] [cycles=100] [speed=1..6]")
quit(1)
return
var data = JSON.parse_string(FileAccess.get_file_as_string(args[0]))
@@ -33,7 +33,11 @@ func run():
if not is_finite(delta) or delta <= 0:
quit(1)
return
for speed in [6]:
var selected_speed: int = 6
for arg in args:
if arg.begins_with("speed="):
selected_speed = clampi(arg.trim_prefix("speed=").to_int(), 1, root.get_node("Global").SPEED_LIMIT)
for speed in [selected_speed]:
var world = load("res://world/owniverse/owniverse.gd").new()
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
world.state = initial.duplicate(true)
+121
View File
@@ -0,0 +1,121 @@
extends SceneTree
## Paired, read-only throughput benchmark for adaptive 5/4/3/2 Hz cadence.
const E = preload("res://world/owniverse/math/exact.gd")
const SAVE_PATHS: Array[String] = [
"res://tests/artifacts/all_objects_speed6.adventure.json",
"res://tests/artifacts/many_objects_fragmented_stars_cycle.adventure.json"
]
const RESULT_PATH := "res://tests/artifacts/dynamic_cadence_results.json"
var cycles: int = 40
var repeats: int = 2
func _initialize() -> void:
call_deferred("run")
func restore_json_integers(value):
if value is float and value == floorf(value) and absf(value) <= 9007199254740991:
return int(value)
if value is Dictionary:
for key in value: value[key] = restore_json_integers(value[key])
elif value is Array:
for i in value.size(): value[i] = restore_json_integers(value[i])
return value
func create_world(initial: Dictionary, use_adaptive_cadence: bool):
var world = load("res://world/owniverse/owniverse.gd").new()
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
world.state = initial.duplicate(true)
world.speed = 6
world.adaptive_cadence = use_adaptive_cadence
world.frame_step_seconds = 0.2
world.cadence_level = 0
return world
func dispose(world) -> void:
for entity in world.catalog.entities.values(): entity.free()
world.free()
func measure(initial: Dictionary, adaptive: bool, save_name: String, repeat_index: int) -> Dictionary:
var world = create_world(initial, adaptive)
var elapsed_usec: int = 0
var requested_seconds: float = 0.0
var step_counts: Dictionary = {}
var samples: Array[float] = []
var initial_clock: Variant = world.state.time.datetime
for i in cycles:
var step: float = world.frame_step_seconds
step_counts[str(step)] = int(step_counts.get(str(step), 0)) + 1
requested_seconds += step
var started: int = Time.get_ticks_usec()
world.evolute(step)
var usec: int = Time.get_ticks_usec() - started
elapsed_usec += usec
samples.append(float(usec) / 1000.0)
var applied_years: Variant = E.subtract(world.state.time.datetime, initial_clock)
var expected_years: float = requested_seconds * 100000.0
assert(absf(E.approx(applied_years) - expected_years) <= maxf(1.0, expected_years * 1e-10), "Clock advance differs from accumulated cadence interval")
var validation: String = root.get_node("SaveManager").validate_data({
"format_version": 6,
"app_version": "0.7.0",
"ui": {"current_page": 0},
"state": world.state
})
assert(validation.is_empty(), validation)
samples.sort()
var result: Dictionary = {
"save": save_name,
"mode": "adaptive_5_4_3_2" if adaptive else "fixed_5",
"repeat": repeat_index,
"cycles": cycles,
"mean_ms_per_cycle": float(elapsed_usec) / (cycles * 1000.0),
"p95_ms": samples[int(ceil(samples.size() * 0.95)) - 1],
"max_ms": samples.back(),
"cpu_cycles_per_second": float(cycles) * 1000000.0 / elapsed_usec,
"simulated_seconds_per_cpu_second": requested_seconds * 1000000.0 / elapsed_usec,
"requested_simulation_seconds": requested_seconds,
"applied_game_years": E.fixed(applied_years),
"cadence_steps": step_counts,
"final_cadence_hz": 1.0 / world.frame_step_seconds,
"final_adaptive_quality": world.adaptive_quality
}
print("CADENCE_RESULT ", JSON.stringify(result))
dispose(world)
return result
func run() -> void:
for arg in OS.get_cmdline_user_args():
if arg.begins_with("cycles="): cycles = maxi(1, arg.trim_prefix("cycles=").to_int())
if arg.begins_with("repeats="): repeats = maxi(1, arg.trim_prefix("repeats=").to_int())
var inputs: Dictionary = {}
for path in SAVE_PATHS:
var parsed = JSON.parse_string(FileAccess.get_file_as_string(path))
assert(parsed is Dictionary and parsed.get("format_version") == 6, "Invalid benchmark save: " + path)
var payload: Dictionary = E.decode(restore_json_integers(parsed))
var validation: String = root.get_node("SaveManager").validate_data(payload)
assert(validation.is_empty(), validation)
inputs[path.get_file()] = payload.state
var results: Array[Dictionary] = []
for save_name in inputs:
var initial: Dictionary = inputs[save_name]
# Alternate order to limit machine warm-up and thermal bias.
for repeat_index in repeats:
if repeat_index % 2 == 0:
results.append(measure(initial, false, save_name, repeat_index))
results.append(measure(initial, true, save_name, repeat_index))
else:
results.append(measure(initial, true, save_name, repeat_index))
results.append(measure(initial, false, save_name, repeat_index))
var output := FileAccess.open(RESULT_PATH, FileAccess.WRITE)
assert(output != null)
output.store_string(JSON.stringify({
"machine": OS.get_model_name(),
"godot": Engine.get_version_info(),
"speed": 6,
"cycles_per_run": cycles,
"repeats": repeats,
"input_saves_unchanged": true,
"results": results
}, "\t", true, true))
output.close()
print("Wrote paired cadence results: ", RESULT_PATH)
quit()
+1
View File
@@ -0,0 +1 @@
uid://3fogff2lfmy5
+116
View File
@@ -0,0 +1,116 @@
extends SceneTree
## Measures real Stage scheduling on the full-load diagnostic save.
const E = preload("res://world/owniverse/math/exact.gd")
const RUN_SECONDS: float = 10.0
const RESULT_PATH := "res://tests/artifacts/dynamic_cadence_ui_results.json"
func _initialize() -> void:
call_deferred("run")
func restore(value):
if value is float and value == floorf(value) and absf(value) <= 9007199254740991: return int(value)
if value is Dictionary:
for key in value: value[key] = restore(value[key])
elif value is Array:
for i in value.size(): value[i] = restore(value[i])
return value
func prepare(stage, state: Dictionary, adaptive: bool) -> void:
var world = stage.owniverse
world.state = state.duplicate(true)
world.speed = 6
world.adaptive_cadence = adaptive
world.adaptive_cycle_ema_ms = 0.0
world.adaptive_quality = 0
world.adaptive_recovery_cycles = 0
world.cadence_level = 0
world.cadence_recovery_cycles = 0
world.frame_step_seconds = 0.2
world.frame_elapsed_seconds = 0.0
world.frame_clock = world.state.time.duplicate()
world.frame_active_clock = {}
world._resume_clock = {}
world._queued_productions.clear()
world.production_queue_busy = false
world.frame_busy = false
world.frame_commits = 0
world.profile.clear()
stage.frame_samples.clear()
stage.process_samples.clear()
stage.previous_frame = 0
stage.measuring = false
world.presentation.refresh(world.state, world.catalog)
stage.set_owniverse_item_value()
func run_measurement(stage, initial: Dictionary, adaptive: bool) -> Dictionary:
prepare(stage, initial, adaptive)
var world = stage.owniverse
var start_usec: int = Time.get_ticks_usec()
var requested_start: Variant = world.frame_clock.datetime
var published_start: Variant = world.state.time.datetime
stage.measuring = true
while float(Time.get_ticks_usec() - start_usec) / 1000000.0 < RUN_SECONDS:
await process_frame
stage.measuring = false
var measured_frames: int = stage.frame_samples.size()
# Let the final atomic transaction publish before starting the paired run.
while world.frame_busy or world.production_queue_busy:
await process_frame
var elapsed_usec: int = Time.get_ticks_usec() - start_usec
var requested: Variant = E.subtract(world.frame_clock.datetime, requested_start)
var published: Variant = E.subtract(world.state.time.datetime, published_start)
var samples: Array = stage.frame_samples.duplicate()
var total_frame_ms: float = 0.0
for value in samples: total_frame_ms += value
samples.sort()
var result: Dictionary = {
"mode": "adaptive_5_4_3_2" if adaptive else "fixed_5",
"frames": measured_frames,
"elapsed_seconds": float(elapsed_usec) / 1000000.0,
"commits": world.frame_commits,
"completed_cycles_per_second": float(world.frame_commits) * 1000000.0 / elapsed_usec,
"requested_years": E.fixed(requested),
"published_years": E.fixed(published),
"published_to_requested": snappedf(E.approx(E.divide(published, requested)), 0.00001),
"mean_render_fps": float(samples.size()) * 1000.0 / total_frame_ms if total_frame_ms > 0.0 else 0.0,
"render_p95_ms": samples[int(ceil(samples.size() * 0.95)) - 1] if not samples.is_empty() else 0.0,
"render_max_ms": samples.back() if not samples.is_empty() else 0.0,
"final_cadence_hz": 1.0 / world.frame_step_seconds,
"final_adaptive_quality": world.adaptive_quality,
"cpu_profile_usec_calls": world.profile.duplicate(true)
}
print("UI_CADENCE_RESULT ", JSON.stringify(result))
return result
func run() -> void:
var raw = JSON.parse_string(FileAccess.get_file_as_string("res://tests/artifacts/all_objects_speed6.adventure.json"))
assert(raw is Dictionary and raw.get("format_version") == 6)
var payload: Dictionary = E.decode(restore(raw))
var validation: String = root.get_node("SaveManager").validate_data(payload)
assert(validation.is_empty(), validation)
var scene = load("res://world/world.tscn").instantiate()
var stage = scene.get_node("Stage")
stage.owniverse.free()
stage.set_script(load("res://tests/fixtures/benchmark_ui_stage.gd"))
root.add_child(scene)
await process_frame
stage.owniverse.profile_enabled = true
var results: Array[Dictionary] = []
for repeat_index in 2:
if repeat_index % 2 == 0:
results.append(await run_measurement(stage, payload.state, false))
results.append(await run_measurement(stage, payload.state, true))
else:
results.append(await run_measurement(stage, payload.state, true))
results.append(await run_measurement(stage, payload.state, false))
var report: Dictionary = {"machine": OS.get_model_name(), "godot": Engine.get_version_info(), "speed": 6, "wall_seconds_per_run": RUN_SECONDS, "save": "all_objects_speed6.adventure.json", "results": results}
var output := FileAccess.open(RESULT_PATH, FileAccess.WRITE)
assert(output != null)
output.store_string(JSON.stringify(report, "\t", true, true))
output.close()
print("UI_CADENCE_RESULTS ", JSON.stringify(report))
print("Wrote paired UI cadence results: ", RESULT_PATH)
for entity in stage.owniverse.catalog.entities.values(): entity.free()
stage.owniverse.free()
scene.free()
quit()
@@ -0,0 +1 @@
uid://olp81bbycovp
+129
View File
@@ -0,0 +1,129 @@
extends SceneTree
const E = preload("res://world/owniverse/math/exact.gd")
const Controller = preload("res://ui/achievements/achievement_controller.gd")
var failures := 0
class MockWorld extends RefCounted:
var state := {"time": {"year": 0}, "stats": {"total_amount": {}}}
var presentation := {"revision": 0}
class MockStage extends Control:
const PAGE_WIDTH = 1080
var owniverse = MockWorld.new()
var screens: Dictionary
var stage_page_scroller: ScrollContainer
func _initialize() -> void:
call_deferred("run")
func check(value: bool, message: String) -> void:
if not value:
failures += 1
push_error(message)
func node(parent: Node, child: Node, label: String) -> Node:
child.name = label
parent.add_child(child)
return child
func run() -> void:
check(load("res://stage/stage.gd") != null, "Stage integration compiles")
var scene: PackedScene = load("res://world/world.tscn")
var scene_state: SceneState = scene.get_state()
for language in ["English", "Russian"]:
var connected := false
for i in scene_state.get_connection_count():
if str(scene_state.get_connection_source(i)) == "Stage/MainMenu/Language/" + language:
connected = scene_state.get_connection_signal(i) == &"pressed" and str(scene_state.get_connection_target(i)) == "Stage/MainMenu" and scene_state.get_connection_method(i) == &"_on_change_locale_pressed" and scene_state.get_connection_binds(i) == ["en" if language == "English" else "ru"]
check(connected, language + " button connects to locale handler")
var stage := MockStage.new()
root.add_child(stage)
var world = node(stage, Control.new(), "World")
stage.screens = {"World": world}
stage.stage_page_scroller = node(world, ScrollContainer.new(), "PageScroller")
var pages = node(stage.stage_page_scroller, HBoxContainer.new(), "HBoxContainer")
for names in [["ForcePage", "ForceHeader"], ["ResourcePage", "ResourceHeader"], ["StarPage", "StarHeader"], ["StructurePage", "StructureHeader"], ["LifePage", "LifeHeader"]]:
node(node(pages, VBoxContainer.new(), names[0]), HBoxContainer.new(), names[1])
var screen = node(stage, Control.new(), "Achievements")
var title = node(node(screen, Control.new(), "Header"), TextureRect.new(), "Title")
node(node(screen, ScrollContainer.new(), "AchievementList"), VBoxContainer.new(), "AchievementItems")
var controller = Controller.new()
controller.progress_path = "/tmp/owniverse-achievements-test-%s.cfg" % OS.get_process_id()
stage.add_child(controller)
controller.initialize(stage)
check(controller.definitions.size() == 72, "All 72 enabled achievements loaded")
check(not controller.definitions.has("a95"), "Ignored achievement excluded")
controller.update(0.0)
check(controller.queue.is_empty() and controller.current.is_empty(), "Initially visible achievements do not notify")
stage.owniverse.state.stats.total_amount.H = E.of("5E4")
stage.owniverse.presentation.revision += 1
controller.update(0.0)
check(controller.progress.a13.unlocked, "Cumulative hydrogen threshold unlocks")
check(controller.progress.a14.visible and not controller.progress.a14.unlocked, "Opening threshold reveals next achievement")
controller.queue.clear()
controller._enqueue("a14", "g")
controller._enqueue("a14", "c")
check(controller.queue.size() == 1 and controller.queue[0].type == "c", "Pending reveal upgrades to completion")
controller.queue.clear()
controller.current = {}
stage.owniverse.state.time.year = E.of("1E12")
stage.owniverse.state.stats.total_amount.H = E.of("1E24")
stage.owniverse.state.stats.total_amount.Star = E.of("9E16")
stage.owniverse.presentation.revision += 1
controller.update(0.0)
check(controller.progress.a4.unlocked and controller.progress.a20.unlocked and controller.progress.a71.unlocked, "Exact large thresholds unlock")
check(controller.current.type == "c", "Immediate completion emits only completed notification")
var count: int = controller.queue.size()
controller.update(0.0)
check(controller.queue.size() == count, "Repeated update does not duplicate events")
world.hide()
var time: float = controller.elapsed
controller.update(5.0)
check(controller.elapsed == time, "Menu pauses notification")
world.show()
controller.update(3.4)
check(controller.current.is_empty() and not controller.panel.visible, "Notification expires")
for header in controller.headers: check(header.modulate.a == 1.0, "Indicators restored")
controller.update(0.0)
var remaining: int = controller.queue.size()
var click := InputEventMouseButton.new()
click.button_index = MOUSE_BUTTON_LEFT
click.pressed = true
controller._on_notification_input(click)
check(controller.current.is_empty() and not controller.panel.visible, "Click dismisses notification")
check(controller.queue.size() == remaining, "Click preserves pending notifications")
for header in controller.headers: check(header.modulate.a == 1.0, "Click restores indicators")
stage.owniverse = MockWorld.new()
controller.update(0.0)
check(controller.progress.a71.unlocked, "New universe preserves unlocked achievements")
controller.progress.clear()
controller._load_progress()
check(controller.progress.a71.unlocked, "Progress survives reload")
# Every enabled definition must be reachable through its actual compiled rule.
for id in controller.definitions:
var rule: Dictionary = controller.conditions[id].achieve
var source := {"time": {"year": 0}, "stats": {"total_amount": {}}}
if rule.type == "age": source.time.year = E.subtract(rule.amount, 1)
else: source.stats.total_amount[rule.id] = E.subtract(rule.amount, 1)
check(not controller._matches(rule, source), id + " stays locked below threshold")
if rule.type == "age": source.time.year = rule.amount
else: source.stats.total_amount[rule.id] = rule.amount
check(controller._matches(rule, source), id + " completes at exact threshold")
var global: Node = root.get_node("Global")
var previous_locale: String = global.Locale
global.Locale = "ru"
controller.update(0.0)
check(controller.status.text.begins_with("Открыто"), "Status localizes")
check(title.texture == Controller.TITLES.ru, "Russian title selected")
check(controller.items.a71.event_label.text.contains("90 квадриллионов"), "Russian achievement text selected")
global.Locale = "en"
controller.update(0.0)
check(controller.status.text.begins_with("Unlocked"), "English status selected")
check(title.texture == Controller.TITLES.en, "English title selected")
check(controller.items.a71.event_label.text.contains("90 quadrillion"), "English achievement text selected")
controller.current = {"id": "a71", "type": "c"}
controller._display_event(controller.current)
check(controller.notification_item.event_label.text.contains("90 quadrillion"), "English notification text selected")
global.Locale = "ru"
controller.update(0.0)
check(controller.notification_item.event_label.text.contains("90 квадриллионов"), "Active notification changes language")
global.Locale = previous_locale
var path: String = controller.progress_path
stage.free()
DirAccess.remove_absolute(path)
print("Achievement checks: ", "PASS" if failures == 0 else "FAIL")
quit(1 if failures else 0)
+1
View File
@@ -0,0 +1 @@
uid://uslcyxm8vpcn
+4
View File
@@ -38,6 +38,10 @@ func run() -> void:
actions.set_actions("step", 123, func ( request ) : return E.multiply(E.floor_value(E.divide(request, 5)), 5))
check(E.eq(actions.action_values.values(), [10, 100, 123, 0, 0]), "Unproducible small requests omitted")
check(E.eq(actions.action_labels[2].text, "120"), "Recipe preview displayed while original request retained")
actions.mark_pending(123)
check(actions.action_labels[2].text == "…" and actions.action_labels[2].get_parent().get_node("TextureButton").disabled, "Queued action is visibly pending and cannot be duplicated")
actions.set_actions("step", 123, func ( request ) : return E.multiply(E.floor_value(E.divide(request, 5)), 5))
check(actions.action_labels[2].text == "120" and not actions.action_labels[2].get_parent().get_node("TextureButton").disabled, "Action refresh clears pending state")
var emitted: Array = []
actions.action_pressed.connect(func ( _page , amount ) : emitted.append(amount))
actions._on_action_button_pressed(2)
+57
View File
@@ -0,0 +1,57 @@
extends SceneTree
var checks: int = 0
var failures: int = 0
func _initialize() -> void:
call_deferred("run")
func check(condition: bool, message: String) -> void:
checks += 1
if not condition:
failures += 1
push_error(message)
func run() -> void:
var world = load("res://world/owniverse/owniverse.gd").new()
world.adaptive_cadence = false
world.update_adaptive_quality(250000)
check(world.adaptive_quality == 2, "A slow cycle selects the medium approximation")
world.update_adaptive_quality(250000)
check(world.adaptive_quality == 3, "A slow medium-approximation cycle selects the coarse mode")
check(world.adaptive_generation_phase == 1, "Coarse generation phase alternates")
for i in 36: world.update_adaptive_quality(100000)
check(world.adaptive_quality == 2, "Thirty consecutive recovery cycles lower quality by one level")
for i in 30: world.update_adaptive_quality(100000)
check(world.adaptive_quality == 1, "Sustained headroom lowers quality gradually")
for i in 30: world.update_adaptive_quality(100000)
check(world.adaptive_quality == 0, "The exact mode returns after sustained headroom")
var cadence_world = load("res://world/owniverse/owniverse.gd").new()
for i in 3: cadence_world.update_adaptive_quality(350000)
check(cadence_world.cadence_level == 3, "Sustained 350 ms cycles select the 2 Hz cadence")
check(is_equal_approx(cadence_world.frame_step_seconds, 0.5), "The lowest cadence uses a 0.5 second frame step")
cadence_world.free()
var rows: Array = []
for node in 12:
var edges: Dictionary = {}
var outgoing: float = 0.0
if node < 6:
edges[node + 6] = 0.25
outgoing = 0.25
rows.append({"edges": edges, "outgoing": outgoing})
var generator: Dictionary = {"rows": rows, "index": {"source": 0}}
var solved: Dictionary = world.civilization_flow._solve_single_step(generator, "source", 2.0)
for ethic in 6:
var mass: float = 0.0
var occupation: float = 0.0
for node in range(ethic, 12, 6):
mass += solved.mass[node]
occupation += solved.occupation[node]
check(absf(mass - 1.0) < 1e-12, "Approximate transition conserves ethic mass %d" % ethic)
check(absf(occupation - 2.0) < 1e-12, "Approximate transition conserves integrated time %d" % ethic)
world.free()
print("Adaptive quality: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)
+1
View File
@@ -0,0 +1 @@
uid://ct356urggcgal
+78
View File
@@ -0,0 +1,78 @@
extends "res://tests/test_civilization_evolution.gd"
func run():
var rules: Dictionary = root.get_node("Global").CivilizationRules
for count in [103, E.of("10000000000000000000000000000000000000103"), E.add(E.of("1e400"), 103)]:
var world = fixture()
var third: Variant = E.floor_value(E.divide(count, 3))
var free: Variant = E.subtract(count, E.multiply(third, 2))
world.state.amount["C0.2"] = free
world.state.binds.RD = {"C0.2": third}
world.state.binds.OD = {"C0.2": third}
var counts: Dictionary = {"C0.2": third, "C0.3": third, "HP": free}
var profile: Dictionary = {"m": 0.4, "l": 0.2, "w": 0.1, "d": 0.1, "c": 0.1, "b": 0.1}
var profiles: Dictionary = {}
for id in counts: profiles[id] = profile
var physical: Dictionary = world.ledger.physical_amounts(world.state)
var initial_physical: Dictionary = physical.duplicate()
var committed: Dictionary = world.civilization_flow._commit(world.state, physical, {"C0.2": {"counts": counts, "profiles": profiles}}, rules, 100)
check(E.eq(committed, world.ledger.physical_amounts(world.state)), "Commit snapshot matches a full physical rebuild")
check(E.eq(physical, initial_physical), "Commit does not mutate its input snapshot")
for id in counts:
check(E.eq(world.ledger.physical_amount(world.state, id), counts[id]), "Commit preserves exact destination quota: " + id)
for owner in ["RD", "OD"]:
var held: Variant = 0
for id in counts: held = E.add(held, world.state.binds[owner].get(id, 0))
check(E.eq(held, third), "Commit preserves each owner's exact population")
for id in ["C0.2", "C0.3"]:
for ethic in profile: check(absf(world.state.ethics[id][ethic] - profile[ethic]) < 1e-12, "Commit preserves incoming ethical profile")
invariants(world)
dispose(world)
world = fixture()
world.state.binds.RD = {"C4.4": count}
var terminal: Dictionary = {"Ct": State.empty_profile()}
world.civilization_flow._commit(world.state, world.ledger.physical_amounts(world.state), {"C4.4": {"counts": {"Ct": count}, "profiles": terminal}}, rules, 100)
check(E.eq(world.state.amount.Ct, count), "Transcendent population is entirely free")
check(E.eq(world.state.binds.RD.get("Ct", 0), 0), "Transcendent population cannot retain a stellar owner")
dispose(world)
world = fixture()
world.state.amount["C2.0"] = count
var initial_capacity: Dictionary = world.ledger.physical_amounts(world.state)
var frozen_capacity: Dictionary = initial_capacity.duplicate()
var constrained: Dictionary = State.enforce_capacity(world, world.state, rules, 100, initial_capacity)
check(E.eq(constrained, world.ledger.physical_amounts(world.state)), "Cascading capacity changes maintain exact physical totals")
check(E.eq(initial_capacity, frozen_capacity), "Capacity correction does not mutate the previous snapshot")
dispose(world)
world = fixture()
var original_owners: Dictionary = {}
for owner in ["RD", "OD", "YD", "WYD"]:
world.state.binds[owner] = {}
for resource in ["GG", "AB", "RP", "HP"]:
world.state.binds[owner][resource] = count
original_owners[owner] = E.multiply(count, 4)
var colonies: Dictionary = {
"C1.4": {"counts": {"C1.4": E.multiply(count, 3)}, "profiles": {"C1.4": profile}},
"C1.5": {"counts": {"C1.5": E.multiply(count, 5)}, "profiles": {"C1.5": profile}}
}
var physical_before: Dictionary = world.ledger.physical_amounts(world.state)
var untouched: Dictionary = physical_before.duplicate()
var colonized: Dictionary = world.civilization_flow._colonize(world.state, colonies, 100, rules, physical_before)
check(E.eq(colonized, world.ledger.physical_amounts(world.state)), "Colonization snapshot matches a full physical rebuild")
check(E.eq(physical_before, untouched), "Colonization does not mutate its input snapshot")
var capacity: Dictionary = State.enforce_capacity(world, world.state, rules, 100, colonized)
check(E.eq(capacity, world.ledger.physical_amounts(world.state)), "Capacity snapshot matches a full physical rebuild")
check(E.eq(world.ledger.physical_amount(world.state, "C1.4"), E.multiply(count, 3)), "Colonization credits exact first quota")
check(E.eq(world.ledger.physical_amount(world.state, "C1.5"), E.multiply(count, 5)), "Colonization credits exact second quota")
check(E.eq(world.state.stats.total_amount["C1.4"], E.multiply(count, 3)), "Consolidated credit preserves creation statistics")
check(E.eq(world.state.civilization_stats.colonies, E.multiply(count, 8)), "Colonization statistics equal exact granted total")
for owner in original_owners:
var held: Variant = 0
for id in ["GG", "AB", "RP", "HP", "C1.4", "C1.5"]: held = E.add(held, world.state.binds[owner].get(id, 0))
check(E.eq(held, original_owners[owner]), "Colonization preserves each owner's occupied worlds")
var remaining_planets: Variant = 0
for id in ["GG", "AB", "RP", "HP"]: remaining_planets = E.add(remaining_planets, world.ledger.physical_amount(world.state, id))
check(E.eq(remaining_planets, E.multiply(count, 8)), "Planet loss equals colonies created")
invariants(world)
dispose(world)
print("Flow commit: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)
+1
View File
@@ -0,0 +1 @@
uid://bfgdsxoycr20e
+26 -8
View File
@@ -38,21 +38,25 @@ func run():
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)
check(E.eq(sliced.display_clock().year, Clock.advance({"datetime": 0, "year": 0}, 0.2, sliced.speed).year), "Display clock advances independently of the heavy transaction")
check(sliced.request_production("H", 1, true), "Production requested during a frame is queued")
check(sliced.frame_checkpoint().productions.size() == 1, "Queued production remains part of the save checkpoint")
check(E.eq(sliced.state.amount.H, initial_h), "Player action waits for transaction commit")
sliced.evolute_frame(0.01, self)
var requested: Dictionary = sliced.frame_clock.duplicate()
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
while sliced.frame_busy or sliced.production_queue_busy: await process_frame
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")
sliced.evolute_frame(0.001, self)
var second_clock: Dictionary = Clock.advance(requested, 0.001, 3)
check(not sliced.frame_busy, "Speed change does not bypass real-time cadence")
sliced.evolute_frame(0.189, self)
var second_clock: Dictionary = Clock.advance(Clock.advance(requested, 0.001, 3), 0.189, 3)
var active: Dictionary = sliced.frame_active_clock.duplicate()
while sliced.frame_busy: await process_frame
while sliced.frame_busy or sliced.production_queue_busy: await process_frame
direct.evolution.evolute_to_datetime(active.datetime, active)
check(E.eq(direct.state, sliced.state), "Bounded frame transaction matches its exact target clock")
check(E.eq(sliced.frame_clock, second_clock), "All elapsed time during a busy frame remains in the requested clock")
@@ -75,7 +79,7 @@ func run():
expected.state = published.duplicate(true)
resumed.restore_frame_checkpoint(checkpoint)
resumed.evolute_frame(0.0, self)
while resumed.frame_busy: await process_frame
while resumed.frame_busy or resumed.production_queue_busy: await process_frame
expected.evolution.evolute_to_datetime(expected_clock.datetime, expected_clock)
expected.production.produce_hs("H", 123)
expected.speed = 3
@@ -111,11 +115,25 @@ func run():
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:
check(E.eq(behind.state.time.datetime, 100000) and E.eq(behind.state.time, behind.frame_clock), "Delayed start consumes all elapsed time in one cycle")
behind.evolute_frame(0.0, self)
check(behind.frame_commits == 1 and not behind.frame_busy, "Consumed interval cannot be processed twice")
var next_interval: float = behind.frame_step_seconds + 0.03
behind.evolute_frame(next_interval, self)
var remained_busy: bool = behind.frame_busy
if remained_busy: behind.evolute_frame(0.53, self)
var pending_clock: Dictionary = behind.frame_clock.duplicate()
if remained_busy:
var expected_active: Dictionary = Clock.advance({"datetime": 100000, "year": 100000}, next_interval, behind.speed)
check(E.eq(behind.frame_active_clock, expected_active), "Busy transaction reserves its original target")
while behind.frame_busy: await process_frame
if remained_busy:
check(E.eq(behind.state.time, behind.frame_active_clock), "Busy transaction keeps its original target")
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")
check(E.eq(behind.state.time, pending_clock), "Next cycle consumes all time accumulated during computation")
else:
check(E.eq(behind.state.time, pending_clock), "Completed transaction publishes the full requested interval")
dispose(behind)
print("Frame evolution: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)
+15 -2
View File
@@ -59,14 +59,27 @@ func run() -> void:
world.state.auto_resources = {"H": 10}
world.state.batches.H = [{"amount": 10, "start_year": 1, "end_year": 2}]
var before: Dictionary = world.state.duplicate(true)
check((not world.production.try_production(world.state, world.catalog.entities.N, 1, true)), "Binding failure rejects production")
var physical: Dictionary = world.ledger.physical_amounts(world.state)
var frozen_physical: Dictionary = physical.duplicate()
check((not world.production.try_production(world.state, world.catalog.entities.N, 1, true, physical)), "Binding failure rejects production")
check(E.eq(physical, frozen_physical), "Failed production preserves the shared quantity snapshot")
check(E.eq(world.state, before), "Failure preserves costs, batches and budget")
world.state.amount.S = 10
physical = world.ledger.physical_amounts(world.state)
var old_batches: Array = world.state.batches.H
check(world.production.try_production(world.state, world.catalog.entities.N, 2, true), "Funded production succeeds")
check(world.production.try_production(world.state, world.catalog.entities.N, 2, true, physical), "Funded production succeeds")
check(E.eq(physical, world.ledger.physical_amounts(world.state)), "Successful production updates exact totals without changing bound-resource totals")
check((E.eq(world.state.amount.H, 6) and E.eq(world.state.amount.S, 4)), "Exact cost and binding")
check(E.eq(world.state.auto_resources.H, 6), "Exact automatic budget")
check((E.eq(old_batches[0].amount, 10) and E.eq(world.state.batches.H[0].amount, 6)), "Consumed batches isolated")
for member in world.catalog.members("Star"): world.state.amount[member] = 0
var star_stocks: Array = ["1234567890123456789", "876543210987654321", "3141592653589793238", "2718281828459045235", "1414213562373095048", "1732050807568877293", "2236067977499789696", "2449489742783178098", "3162277660168379331"]
for i in star_stocks.size(): world.state.amount[world.catalog.members("Star")[i]] = E.of(star_stocks[i])
var scd_request: Variant = E.of("1e15")
var scd_created: Variant = world.production.produce("SCD", scd_request)
check(E.eq(scd_created, scd_request), "Large SCD production distributes a whole star quota exactly")
check(E.eq(world.state.amount.SCD, scd_request), "Large SCD creation is committed")
print(("Transaction: %d checks, %d failures" % [checks, failures]))
dispose(world)
quit((1 if failures else 0))
+36
View File
@@ -0,0 +1,36 @@
extends SceneTree
const E = preload("res://world/owniverse/math/exact.gd")
const Report = preload("res://ui/report/report.gd")
class Ledger extends RefCounted:
func physical_amounts(_source: Dictionary) -> Dictionary:
return {"RD": 3, "HG": 1}
class Catalog extends RefCounted:
var entities: Dictionary = {}
func is_group(_id: String) -> bool: return false
class World extends RefCounted:
var catalog = Catalog.new()
var ledger = Ledger.new()
var state = {"amount": {}, "stats": {"total_amount": {"H": 1234}}, "world_parameters": {"Federation": 1, "Enlightenment": 1, "Order": 1}}
func _initialize() -> void:
call_deferred("run")
func run() -> void:
var global: Node = root.get_node("Global")
var world = World.new()
world.state.stats.total_amount.H = E.of("1234567890123456789012345")
var before: Dictionary = E.encode(world.state)
global.Locale = "en"
var text: String = Report.build(world, global)
assert(text.contains("75.00%") and text.contains("25.00%"), text)
assert(text.contains("1 234 567 890 123 456 789 012 345"), text)
assert(text.contains("Total resources obtained") and not text.contains("Total entities emerged"), text)
world.state.world_parameters.Federation = E.of("1.3")
text = Report.build(world, global)
assert(text.contains("Federation:") and text.contains("0.30"), text)
world.state.world_parameters.Federation = 1
global.Locale = "ru"
text = Report.build(world, global)
assert(text.contains("Всего получено ресурсов") and text.contains("Красные карлики"), text)
assert(E.encode(world.state) == before, "Report mutated simulation")
assert(load("res://stage/stage.gd") != null)
print("Universe report checks: PASS")
quit()
+1
View File
@@ -0,0 +1 @@
uid://brb04x12elvqt
+206
View File
@@ -0,0 +1,206 @@
extends Node
## Player-wide achievement progress and the shared in-game notification queue.
const E = preload("res://world/owniverse/math/exact.gd")
const ITEM = preload("res://ui/achievements/achievement_item.tscn")
const TITLES = {
"en": preload("res://ui/achievements/achievements.en.png"),
"ru": preload("res://ui/achievements/achievements.ru.png"),
}
var progress_path: String = "user://achievements.cfg"
const HOLD_SECONDS = 3.0
const FADE_SECONDS = 0.3
var global: Node
var stage: Control
var definitions: Dictionary = {}
var conditions: Dictionary = {}
var progress: Dictionary = {}
var items: Dictionary = {}
var queue: Array[Dictionary] = []
var current: Dictionary = {}
var elapsed: float = 0.0
var last_revision: int = -1
var last_world: Object
var locale: String = ""
var panel: PanelContainer
var notification_item: Control
var headers: Array[Control] = []
var status: Label
var list: VBoxContainer
var storage_available: bool = true
func initialize(owner_stage: Control) -> void:
stage = owner_stage
global = get_tree().root.get_node("Global")
locale = global.Locale
for id in global.Achievements.en:
var row: Dictionary = global.Achievements.en[id]
if str(row.Ignore) == "1": continue
definitions[id] = row
conditions[id] = {
"open": _parse_condition(str(row.OpenCondition)),
"achieve": _parse_condition(str(row.AchieveCondition)),
}
_load_progress()
list = stage.get_node("Achievements/AchievementList/AchievementItems")
for child in list.get_children():
list.remove_child(child)
child.queue_free()
status = Label.new()
status.add_theme_font_size_override("font_size", 32)
list.add_child(status)
var pages: Node = stage.get_node("World/PageScroller/HBoxContainer")
for path in ["ForcePage/ForceHeader", "ResourcePage/ResourceHeader", "StarPage/StarHeader", "StructurePage/StructureHeader", "LifePage/LifeHeader"]:
headers.append(pages.get_node(path))
panel = PanelContainer.new()
panel.name = "AchievementNotification"
panel.set_as_top_level(true)
panel.mouse_filter = Control.MOUSE_FILTER_STOP
panel.gui_input.connect(_on_notification_input)
panel.z_index = 20
stage.get_node("World").add_child(panel)
notification_item = ITEM.instantiate()
panel.add_child(notification_item)
_ignore_notification_children(notification_item)
panel.hide()
_rebuild_list()
func _parse_condition(expression: String) -> Dictionary:
if expression.is_empty(): return {}
var parts := expression.split(":")
assert(parts.size() == 3 and parts[0] in ["age", "total"], "Unsupported achievement condition: " + expression)
return {"type": parts[0], "id": parts[1], "amount": E.of(parts[2])}
func _matches(condition: Dictionary, source: Dictionary) -> bool:
if condition.is_empty(): return true
var amount: Variant = source.time.year if condition.type == "age" else source.stats.total_amount.get(condition.id, 0)
return E.ge(amount, condition.amount)
func update(delta: float) -> void:
if global.Locale != locale:
locale = global.Locale
_rebuild_list()
if not current.is_empty(): _display_event(current)
var world = stage.owniverse
if world != last_world or world.presentation.revision != last_revision:
last_world = world
last_revision = world.presentation.revision
_check(world.state)
# Menu time does not consume a notification's display time.
if not stage.screens.World.visible: return
if current.is_empty() and not queue.is_empty():
current = queue.pop_front()
elapsed = 0.0
_display_event(current)
if current.is_empty(): return
elapsed += delta
var page: int = clampi(roundi(float(stage.stage_page_scroller.scroll_horizontal) / stage.PAGE_WIDTH), 0, headers.size() - 1)
for i in headers.size():
headers[i].modulate.a = 0.0 if i == page else 1.0
panel.global_position = headers[page].global_position
panel.size = Vector2(headers[page].size.x, maxf(128.0, headers[page].size.y))
panel.modulate.a = clampf(1.0 - (elapsed - HOLD_SECONDS) / FADE_SECONDS, 0.0, 1.0)
if elapsed >= HOLD_SECONDS + FADE_SECONDS:
dismiss_notification()
func _ignore_notification_children(node: Node) -> void:
if node is Control: node.mouse_filter = Control.MOUSE_FILTER_IGNORE
for child in node.get_children(): _ignore_notification_children(child)
func _on_notification_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
panel.accept_event()
dismiss_notification()
elif event is InputEventScreenTouch and event.pressed:
panel.accept_event()
dismiss_notification()
func dismiss_notification() -> void:
panel.hide()
current = {}
elapsed = 0.0
for header in headers: header.modulate.a = 1.0
func _check(source: Dictionary) -> void:
var changed := false
for id in definitions:
var saved: Dictionary = progress.get(id, {"visible": false, "unlocked": false})
if bool(saved.unlocked): continue
var rule: Dictionary = conditions[id]
if _matches(rule.achieve, source):
saved.visible = true
saved.unlocked = true
_enqueue(id, "c")
changed = true
elif not bool(saved.visible) and _matches(rule.open, source):
saved.visible = true
# An empty opening condition makes an item visible without an alert.
if not rule.open.is_empty(): _enqueue(id, "g")
changed = true
progress[id] = saved
if changed:
_save_progress()
_rebuild_list()
func _enqueue(id: String, event_type: String) -> void:
for event in queue:
if event.id == id:
event.type = event_type
return
queue.append({"id": id, "type": event_type})
func _text(id: String) -> String:
var row: Dictionary = global.Achievements[locale][id]
var prefix: String = "Ru" if locale == "ru" else "En"
var title: String = str(row[prefix + "1"])
var description: String = str(row[prefix + "2"])
return "[b]" + title + "[/b]" + ("\n" + description if not description.is_empty() else "")
func _display_event(event: Dictionary) -> void:
notification_item.open(int(definitions[event.id].Points), event.type, _text(event.id))
panel.modulate.a = 1.0
panel.show()
func _rebuild_list() -> void:
var title: TextureRect = stage.get_node_or_null("Achievements/Header/Title")
if title != null: title.texture = TITLES[locale]
var unlocked := 0
var position := 1
for id in definitions:
var saved: Dictionary = progress.get(id, {})
if not bool(saved.get("visible", false)) and not bool(saved.get("unlocked", false)): continue
if not items.has(id):
var item: Control = ITEM.instantiate()
list.add_child(item)
items[id] = item
list.move_child(items[id], position)
position += 1
var completed: bool = bool(saved.get("unlocked", false))
if completed: unlocked += 1
items[id].open(int(definitions[id].Points), "c" if completed else "g", _text(id))
var row: Dictionary = global.get_template("Achievement-Status-Template", "Interface", "Header")
status.text = global.render_template(row, {"count": unlocked, "total": definitions.size()})
func _load_progress() -> void:
var config := ConfigFile.new()
var error := config.load(progress_path)
if error == ERR_FILE_NOT_FOUND: return
if error != OK:
storage_available = false
push_error("Cannot load achievement progress: " + error_string(error))
return
for id in definitions:
progress[id] = {"visible": bool(config.get_value(id, "visible", false)), "unlocked": bool(config.get_value(id, "unlocked", false))}
func _save_progress() -> void:
if not storage_available: return
var config := ConfigFile.new()
# Preserve entries for achievements temporarily disabled in the catalog.
config.load(progress_path)
for id in progress:
config.set_value(id, "visible", bool(progress[id].visible))
config.set_value(id, "unlocked", bool(progress[id].unlocked))
var error := config.save(progress_path + ".tmp")
if error == OK:
error = DirAccess.rename_absolute(progress_path + ".tmp", progress_path)
if error != OK: push_error("Cannot save achievement progress: " + error_string(error))
@@ -0,0 +1 @@
uid://c1x220s0rloxc
+28
View File
@@ -0,0 +1,28 @@
extends Control
@onready var icons = {
"1c": $Row/Icon1c,
"1g": $Row/Icon1g,
"3c": $Row/Icon3c,
"3g": $Row/Icon3g,
"6c": $Row/Icon6c,
"6g": $Row/Icon6g,
"10c": $Row/Icon10c,
"10g": $Row/Icon10g,
}
@onready var event_label: RichTextLabel = $Row/Label
## Shows this achievement event with the matching points/state icon.
## event_type is "c" for completed or "g" for revealed but not completed.
func open(level: int, event_type: String, event_text: String) -> void:
var icon_key := str(level) + event_type.to_lower()
if not icons.has(icon_key):
push_warning("Unknown achievement icon: %s" % icon_key)
return
for key: String in icons:
icons[key].visible = key == icon_key
event_label.text = event_text
visible = true
+1
View File
@@ -0,0 +1 @@
uid://b6sqyd1tpljr3
+83
View File
@@ -0,0 +1,83 @@
[gd_scene format=3 uid="uid://cs8a5u3ecf51t"]
[ext_resource type="Script" uid="uid://b6sqyd1tpljr3" path="res://ui/achievements/achievement_item.gd" id="1_w8f20"]
[ext_resource type="Texture2D" uid="uid://cfohmmlwh84gk" path="res://ui/achievements/icons/1c.png" id="2_7ntr3"]
[ext_resource type="Texture2D" uid="uid://k6dqaewr30qe" path="res://ui/achievements/icons/1g.png" id="3_1b655"]
[ext_resource type="Texture2D" uid="uid://bccs26gkwsio3" path="res://ui/achievements/icons/3c.png" id="4_xgo6w"]
[ext_resource type="Texture2D" uid="uid://c7regmaoq0dr5" path="res://ui/achievements/icons/3g.png" id="5_7pkbt"]
[ext_resource type="Texture2D" uid="uid://bl7dr5fapao0x" path="res://ui/achievements/icons/6c.png" id="6_aart1"]
[ext_resource type="Texture2D" uid="uid://bk5morikm4k5o" path="res://ui/achievements/icons/6g.png" id="7_ndc4d"]
[ext_resource type="Texture2D" uid="uid://bs54rppydwlvy" path="res://ui/achievements/icons/10c.png" id="8_20e5y"]
[ext_resource type="Texture2D" uid="uid://bmp7e6lmtaex0" path="res://ui/achievements/icons/10g.png" id="9_3xyrb"]
[node name="AchievementItem" type="Control" unique_id=1273881744]
custom_minimum_size = Vector2(1080, 128)
layout_mode = 3
anchors_preset = 0
size_flags_horizontal = 3
script = ExtResource("1_w8f20")
[node name="Row" type="HBoxContainer" parent="." unique_id=2025386077]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
theme_override_constants/separation = 16
[node name="Icon1c" type="TextureRect" parent="Row" unique_id=2146478339]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("2_7ntr3")
[node name="Icon1g" type="TextureRect" parent="Row" unique_id=2089044466]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("3_1b655")
[node name="Icon3c" type="TextureRect" parent="Row" unique_id=501141005]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("4_xgo6w")
[node name="Icon3g" type="TextureRect" parent="Row" unique_id=844167957]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("5_7pkbt")
[node name="Icon6c" type="TextureRect" parent="Row" unique_id=2137074157]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("6_aart1")
[node name="Icon6g" type="TextureRect" parent="Row" unique_id=616115687]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("7_ndc4d")
[node name="Icon10c" type="TextureRect" parent="Row" unique_id=418038490]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("8_20e5y")
[node name="Icon10g" type="TextureRect" parent="Row" unique_id=1352890506]
visible = false
custom_minimum_size = Vector2(128, 128)
layout_mode = 2
texture = ExtResource("9_3xyrb")
[node name="Label" type="RichTextLabel" parent="Row" unique_id=230945261]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/normal_font_size = 30
theme_override_font_sizes/bold_font_size = 30
bbcode_enabled = true
text = "123"
fit_content = true
scroll_active = false
vertical_alignment = 1
Binary file not shown.
Binary file not shown.
+7
View File
@@ -58,6 +58,13 @@ func _set_action_values(values: Array, displayed: Array) -> void:
action_labels[ind].text = text
action_labels[ind].get_parent().get_node("TextureButton").disabled = E.le(values[ind], 0)
func mark_pending(amount: Variant) -> void:
for ind in range(5):
if not E.eq(action_values[ind], amount): continue
action_labels[ind].text = "…"
action_labels[ind].get_parent().get_node("TextureButton").disabled = true
return
func _format_action_value(value: Variant) -> String:
if ((E.lt(value, 1) or (not E.finite(value))) or E.ne(value, E.floor_value(value))):
BIN
View File
Binary file not shown.
+63
View File
@@ -0,0 +1,63 @@
extends VBoxContainer
const E = preload("res://world/owniverse/math/exact.gd")
const Presentation = preload("res://world/owniverse/item_presentation.gd")
const STARS = ["RD", "OD", "YD", "WYD", "WS", "BG", "SG", "HG"]
const EFFECTS = {"Int.Fed": "Federation", "Int.Enl": "Enlightenment", "Int.Ord": "Order"}
var report_locale: String = ""
@onready var report_body: RichTextLabel = $ReportBody
@onready var report_header: TextureRect = $Header/Header
func _ready() -> void:
visibility_changed.connect(_on_visibility_changed)
func _process(_delta: float) -> void:
if is_visible_in_tree() and report_locale != get_node("/root/Global").Locale:
refresh()
func _on_visibility_changed() -> void:
if is_node_ready() and is_visible_in_tree():
refresh()
report_body.scroll_to_line(0)
func refresh() -> void:
var global: Node = get_node("/root/Global")
report_locale = global.Locale
report_body.text = build(get_parent().owniverse, global)
report_header.texture = load("res://ui/report/" + report_locale + "/report.png")
## Formats existing committed state only. No historical counters or simulation work.
static func build(world, global: Node) -> String:
var source: Dictionary = world.state
var totals: Dictionary = Presentation.amounts(source, world.catalog, true)
var physical: Dictionary = world.ledger.physical_amounts(source)
var star_total: Variant = 0
for id in STARS: star_total = E.add(star_total, physical.get(id, 0))
var blocks: PackedStringArray = []
var lines: PackedStringArray = []
var header: String = ""
# Russian keys provide the common order; get_template resolves both key formats.
for key in global.Templates.ru:
if not str(key).contains(".Report."): continue
var parts: PackedStringArray = str(key).rsplit(".Report.", true, 1)
var id: String = parts[0]
var kind: String = parts[1]
var row: Dictionary = global.get_template(id, "Report", kind)
if kind == "Header":
if not lines.is_empty(): blocks.append(header + "\n\n" + "\n".join(lines))
lines.clear()
header = global.render_template(row, {})
elif kind == "Fraction":
var amount: Variant = physical.get(id, 0)
if E.gt(amount, 0) and E.gt(star_total, 0):
var percent: String = E.grouped(E.mul_div(amount, 100, star_total), 2, false) + "%"
lines.append(global.render_template(row, {"proportion": percent}))
elif kind == "Max":
var amount: Variant = totals.get(id, 0)
if EFFECTS.has(id):
amount = E.subtract(source.world_parameters.get(EFFECTS[id], 1), 1)
if E.gt(amount, 0):
lines.append(global.render_template(row, {"max": E.grouped(amount, 2 if EFFECTS.has(id) else 0, false)}))
if not lines.is_empty(): blocks.append(header + "\n\n" + "\n".join(lines))
return "\n\n".join(blocks)
+1
View File
@@ -0,0 +1 @@
uid://v8h4ov33opyf
BIN
View File
Binary file not shown.
+169
View File
@@ -0,0 +1,169 @@
extends "res://world/owniverse/simulation_module.gd"
class_name OwniverseApocalypseController
const E = preload("res://world/owniverse/math/exact.gd")
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
const TICK_SECONDS: float = 0.1
const END_WAIT_TICKS: int = 20
var _schedule: Array = []
func defaults() -> Dictionary:
return {"phase": 0, "type": 0, "warning": 0, "stage": 1, "tick": 0, "end_ticks": 0}
func check_density(target: Dictionary) -> void:
var apocalypse: Dictionary = target.get("apocalypse", defaults())
if E.gt(apocalypse.get("phase", 0), 0): return
var density: Variant = world.environment.stellar_environment(target).density
var parameters: Dictionary = WorldParameters.extra_params()
var warning: int = 0
if E.ge(density, parameters.bigcrunch3): warning = 3
elif E.ge(density, parameters.bigcrunch2): warning = 2
elif E.ge(density, parameters.bigcrunch1): warning = 1
elif E.le(density, parameters.bigrip3): warning = -3
elif E.le(density, parameters.bigrip2): warning = -2
elif E.le(density, parameters.bigrip1): warning = -1
apocalypse.warning = warning
target.apocalypse = apocalypse
if E.eq(warning, 3):
start(target, 3)
elif E.eq(warning, -3):
start(target, -3)
func start(target: Dictionary, event_type: int) -> void:
var apocalypse: Dictionary = target.get("apocalypse", defaults()).duplicate(true)
if E.gt(apocalypse.phase, 0): return
apocalypse.phase = 1
apocalypse.type = event_type
apocalypse.stage = 1
apocalypse.tick = 0
apocalypse.end_ticks = 0
target.apocalypse = apocalypse
target.speed = 1
target.speed_in_year = 1
_schedule = _make_schedule("SMBH" if event_type > 0 else "S")
func process_tick(target: Dictionary) -> Dictionary:
var result: Dictionary = {"screen": -1, "finished": false}
var apocalypse: Dictionary = target.get("apocalypse", defaults())
if E.ne(apocalypse.get("phase", 0), 1): return result
if _schedule.is_empty(): _schedule = _make_schedule("SMBH" if E.gt(apocalypse.type, 0) else "S")
apocalypse.tick = E.add(apocalypse.tick, 1)
var stage_rows: Array = []
for row: Dictionary in _schedule:
if E.eq(row.stage, apocalypse.stage): stage_rows.append(row)
if stage_rows.is_empty():
var next_stage: Variant = INF
for row: Dictionary in _schedule:
if E.gt(row.stage, apocalypse.stage): next_stage = E.minimum(next_stage, row.stage)
if E.finite(next_stage):
apocalypse.stage = E.integer(next_stage)
apocalypse.tick = 0
else:
_process_final_energy(target, apocalypse)
if E.eq(apocalypse.end_ticks, END_WAIT_TICKS):
apocalypse.phase = 2
result.finished = true
target.apocalypse = apocalypse
return result
var stage_complete: bool = true
for row: Dictionary in stage_rows:
if E.gt(row.tick, apocalypse.tick):
stage_complete = false
continue
var id: String = row.id
if not world.catalog.entities.has(id): continue
if E.gt(world.ledger.physical_amount(target, id), 0):
stage_complete = false
result.screen = int(row.screen)
_destroy_fraction(target, id)
if stage_complete:
var next_stage: Variant = INF
for row: Dictionary in _schedule:
if E.gt(row.stage, apocalypse.stage): next_stage = E.minimum(next_stage, row.stage)
if E.finite(next_stage):
apocalypse.stage = E.integer(next_stage)
apocalypse.tick = 0
else:
_process_final_energy(target, apocalypse)
if E.eq(apocalypse.end_ticks, END_WAIT_TICKS):
apocalypse.phase = 2
result.finished = true
target.apocalypse = apocalypse
world.ledger.refresh_groups(target)
world.ledger.clamp_auto_resources(target)
world.presentation.refresh(target, world.catalog)
return result
func _make_schedule(skipped_id: String) -> Array:
var rows: Array = Global.ApocalypseQueue.values()
rows.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a.Num) < int(b.Num))
var result: Array = []
var stage: int = -1
var tick: int = 0
for source: Dictionary in rows:
if source.Shortcut == skipped_id: continue
var row_stage: int = int(source.Stage)
if row_stage != stage:
stage = row_stage
tick = 0
tick += int(source.Tick)
result.append({"id": str(source.Shortcut), "stage": row_stage, "screen": int(source.Screen), "tick": tick})
return result
func _destroy_fraction(target: Dictionary, id: String) -> void:
var entity: OwniverseEntity = world.catalog.entities[id]
var physical: Variant = world.ledger.physical_amount(target, id)
if E.le(physical, 0): return
var amount: Variant = E.round_value(E.divide(physical, 2)) if E.gt(physical, 5) else (1 if E.ge(physical, 1) else physical)
amount = E.minimum(physical, E.maximum(0, amount))
if E.le(amount, 0): return
var fraction: Variant = E.clamp_value(E.divide(amount, physical), 0, 1)
if E.gt(entity.destroy_level, 0): world.stellar.destroy_planets(target, id, fraction, entity.destroy_level, "apocalypse")
var products: Dictionary = world.stellar.evolution_products(target, entity, amount, {"explosiveness": WorldParameters.effective(target, "Exp")})
var normalized: Dictionary = {}
for output in products:
normalized[output] = E.divide(products[output], amount)
var incoming: Dictionary = world.ledger.transfer_evolution(target, {id: fraction}, {id: normalized})
world.batches.scale_batches(target, id, E.subtract(1, fraction))
var terminal: String = "SMBH" if E.gt(target.apocalypse.type, 0) else "S"
var terminal_yield: Variant = 0
var new_owners: Dictionary = {}
if normalized.is_empty():
terminal_yield = E.multiply(amount, E.add(1, E.maximum(0, entity.mass))) if E.contains(["Star", "BH"], entity.group_as) else amount
for output in normalized:
if E.eq(output, "blast"):
var environment: Dictionary = world.environment.stellar_environment(target)
world.stellar.apply_blast(target, E.multiply(normalized[output], amount), environment)
continue
if not world.catalog.entities.has(output): continue
var output_amount: Variant = E.maximum(0, products[output])
if E.le(output_amount, 0): continue
output_amount = world.ledger.credit(target, output, output_amount)
var output_entity: OwniverseEntity = world.catalog.entities[output]
if E.gt(output_entity.lifespan, 0) or not output_entity.life_rule.is_empty():
world.batches.append_batch(target, output, output_amount, target.time.datetime, target.time.datetime)
new_owners[output] = E.add(new_owners.get(output, 0), output_amount)
world.ledger.accept_transfers(target, incoming)
world.ledger.bind_new_owners(target, new_owners)
if E.gt(terminal_yield, 0): world.ledger.credit(target, terminal, terminal_yield)
world.ledger.round_object_allocations(target)
func _process_final_energy(target: Dictionary, apocalypse: Dictionary) -> void:
var energy: Variant = E.maximum(0, target.energy)
if E.gt(energy, 0):
var amount: Variant = E.round_value(E.divide(energy, 2)) if E.gt(energy, 5) else (1 if E.ge(energy, 1) else energy)
amount = E.minimum(energy, amount)
target.energy = E.maximum(0, E.subtract(energy, amount))
world.ledger.credit(target, "SMBH" if E.gt(apocalypse.type, 0) else "S", amount)
apocalypse.end_ticks = 0
else:
apocalypse.end_ticks = int(apocalypse.end_ticks) + 1
@@ -0,0 +1 @@
uid://u5hs757qtn8c
+25 -8
View File
@@ -14,6 +14,7 @@ func yearly_products(entity: OwniverseEntity) -> Dictionary:
func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Dictionary, distribution: Dictionary, from_year: Variant, to_year: Variant) -> void:
if E.gt(target.get("apocalypse", {}).get("phase", 0), 0): return
var basic: Dictionary = {}
var automatic: Dictionary = {}
for id in quantities:
@@ -72,6 +73,7 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di
# One common multiplier preserves the initial count ratios, even under scarcity.
var candidates: Dictionary = {}
var helium_share: Variant = world.production.stellar_helium_share(previous)
for id in distribution:
var entity: OwniverseEntity = world.catalog.entities[id]
if E.ne(entity.group_as, "Star"):
@@ -87,11 +89,16 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di
return
var costs: Dictionary = {}
var needs: Dictionary = {}
var stellar_hydrogen_cost: Variant = 0
for id in candidates:
if E.le(auto_capacity(target, world.catalog.entities[id], budget), 0):
var entity: OwniverseEntity = world.catalog.entities[id]
if E.le(auto_capacity(target, entity, budget, helium_share), 0):
continue
for resource in entity.cost:
var cost: Variant = E.multiply(E.maximum(0, entity.cost[resource]), candidates[id])
if E.eq(resource, "H") and world.production.uses_stellar_helium(entity):
stellar_hydrogen_cost = E.add(stellar_hydrogen_cost, cost)
continue
for resource in world.catalog.entities[id].cost:
var cost: Variant = E.multiply(E.maximum(0, world.catalog.entities[id].cost[resource]), candidates[id])
costs[resource] = E.add(costs.get(resource, 0), cost)
needs[resource] = E.add(needs.get(resource, 0), cost)
for resource in world.catalog.entities[id].bind:
@@ -100,6 +107,9 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di
for resource in costs:
if E.gt(costs[resource], 0):
scale = E.minimum(scale, E.divide(budget.get(resource, 0), costs[resource]))
if E.gt(stellar_hydrogen_cost, 0):
var hydrogen_budget: Variant = E.minimum(budget.get("H", 0), world.ledger.available(target, "H"))
scale = E.minimum(scale, world.production.stellar_hydrogen_capacity(stellar_hydrogen_cost, hydrogen_budget, world.ledger.available(target, "He"), helium_share))
for resource in needs:
if E.gt(needs[resource], 0):
scale = E.minimum(scale, E.divide(world.ledger.available(target, resource), needs[resource]))
@@ -116,7 +126,8 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di
var order: Array = candidates.keys()
order.sort_custom(func ( a : String , b : String ) -> bool : return (E.lt(candidates[a], candidates[b]) if E.ne(candidates[a], candidates[b]) else E.lt(a, b)))
var created: Dictionary = {}
var metallicity: Variant = world.environment.stellar_environment(target).metallicity
var current_quantities: Dictionary = world.ledger.physical_amounts(target)
var metallicity: Variant = world.environment.stellar_environment(target, current_quantities).metallicity
var total_created: Variant = 0
for id in order:
var entity: OwniverseEntity = world.catalog.entities[id]
@@ -125,10 +136,10 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di
continue
var quota: Variant = E.maximum(0, E.add(E.multiply(candidates[id], scale), deficits.get(id, 0)))
var desired: Variant = E.multiply(E.floor_value(E.divide(quota, step)), step)
var actual: Variant = E.minimum(desired, auto_capacity(target, entity, budget))
var actual: Variant = E.minimum(desired, auto_capacity(target, entity, budget, helium_share))
if E.le(actual, 0):
continue
if (not world.production.try_production(target, entity, actual, true)):
if (not world.production.try_production(target, entity, actual, true, current_quantities, helium_share)):
continue
budget = ordinary_star_budget(target)
created[id] = actual
@@ -138,7 +149,8 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di
deficits[id] = E.subtract(E.add(deficits.get(id, 0), E.multiply(candidates[id], total_created)), created.get(id, 0))
for id in created:
world.ledger.credit(target, id, created[id])
world.batches.append_batch(target, id, created[id], from_year, to_year)
var lifespan_multiplier: Variant = world.production.stellar_lifespan_multiplier(world.catalog.entities[id], helium_share)
world.batches.append_batch(target, id, created[id], from_year, to_year, {}, -INF, null, null, NAN, lifespan_multiplier)
world.stellar.create_planets(target, id, created[id], from_year, to_year, metallicity)
@@ -182,11 +194,16 @@ func ordinary_star_budget(target: Dictionary) -> Dictionary:
return budget
func auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Dictionary) -> Variant:
func auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Dictionary, helium_share_override: Variant = NAN) -> Variant:
var capacity: Variant = INF
var needs: Dictionary = {}
var helium_share: Variant = world.production.stellar_helium_share(source) if not E.finite(helium_share_override) else helium_share_override
for resource in entity.cost:
var cost: Variant = E.maximum(0, entity.cost[resource])
if E.eq(resource, "H") and world.production.uses_stellar_helium(entity):
var hydrogen_budget: Variant = E.minimum(budget.get("H", 0), world.ledger.available(source, "H"))
capacity = E.minimum(capacity, world.production.stellar_hydrogen_capacity(cost, hydrogen_budget, world.ledger.available(source, "He"), helium_share))
continue
needs[resource] = cost
if E.gt(cost, 0):
capacity = E.minimum(capacity, E.floor_value(E.divide(budget.get(resource, 0), cost)))
+2 -2
View File
@@ -6,9 +6,9 @@ const ETHICS = State.ETHICS
const OPPOSITE = {"m": "l", "l": "m", "w": "d", "d": "w", "c": "b", "b": "c"}
# Cache the weighted float worship rate multiplied by the exact population.
static func worship(world, target: Dictionary, rules: Dictionary) -> Variant:
static func worship(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Variant:
var result: Variant = 0
var physical: Dictionary = world.ledger.physical_amounts(target)
if physical.is_empty(): physical = world.ledger.physical_amounts(target)
for id in rules:
if not rules[id].worships: continue
var pop: Dictionary = State.normalize_profile(target.ethics.get(id, {}))
+137 -49
View File
@@ -10,7 +10,7 @@ var _generator_key: String = ""
var _generator: Dictionary = {}
var _solutions: Dictionary = {}
func _edges(target: Dictionary, rules: Dictionary) -> Dictionary:
func _edges(target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
var modifiers: Dictionary = Parameters.life_modifiers(target)
var order: float = maxf(1e-12, E.approx(Parameters.value(target, "Order")))
var stages: Array = rules.keys()
@@ -18,13 +18,13 @@ func _edges(target: Dictionary, rules: Dictionary) -> Dictionary:
var rates: Dictionary = {}
var spans: Dictionary = {}
var progression: Dictionary = {}
var budget: Dictionary = State.stellar_budget(world, target, rules)
var budget: Dictionary = State.stellar_budget(world, target, rules, physical)
var signature: String = str(modifiers) + ":" + str(order)
for id in stages.duplicate():
if rules[id].ethics.is_empty(): continue
rates[id] = Civ.coefficients(world, rules, id, modifiers)
progression[id] = _can_progress(id, rules[id].next, budget, rules)
spans[id] = maxf(1e-12, E.approx(State.effective_span(world, target, id, rules)))
spans[id] = maxf(1e-12, E.approx(State.effective_span(world, target, id, rules, physical)))
signature += id + str(spans[id]) + str(rates[id]) + str(rules[id]) + str(progression[id])
for row: Dictionary in rules[id].ethics.values():
for destination in Q.rule_weights(str(row.fall)):
@@ -100,11 +100,12 @@ func _can_progress(source: String, destination: String, budget: Dictionary, rule
## Uniformization computes exp(Q*t) through a Poisson series of sparse steps.
## Absorbing biological stages and Ct retain their mass. No one-transition cap.
func _solve(generator: Dictionary, source: String, profile: Dictionary, years: float) -> Dictionary:
var key: String = source + ":" + str(years)
var approximation: int = world.adaptive_quality
var key: String = source + ":" + str(years) + ":" + str(approximation)
var basis: Dictionary = _solutions.get(key, {})
if basis.is_empty():
var started: int = world.profile_start()
basis = _solve_basis(generator, source, years)
basis = _solve_single_step(generator, source, years) if approximation > 0 else _solve_basis(generator, source, years)
world.profile_end("civilization_operator_solve", started)
if _solutions.size() >= 512: _solutions.clear()
_solutions[key] = basis
@@ -117,6 +118,35 @@ func _solve(generator: Dictionary, source: String, profile: Dictionary, years: f
occupation[node] *= shares[node % 6]
return {"mass": mass, "occupation": occupation, "occupation_scale": 1.0}
## Load-shedding approximation: each ethic cohort can make at most one
## transition during this interval. The exact Markov solve remains the default.
func _solve_single_step(generator: Dictionary, source: String, years: float) -> Dictionary:
var size: int = generator.rows.size()
var mass: PackedFloat64Array = []
var occupation: PackedFloat64Array = []
mass.resize(size)
occupation.resize(size)
var offset: int = generator.index[source] * 6
for ethic in 6:
var node: int = offset + ethic
var row: Dictionary = generator.rows[node]
var outgoing: float = row.outgoing
if outgoing <= 0.0:
mass[node] = 1.0
occupation[node] = years
continue
var moved: float = clampf(1.0 - exp(-outgoing * years), 0.0, 1.0)
var stayed: float = 1.0 - moved
mass[node] += stayed
occupation[node] += years * (1.0 - moved * 0.5)
for destination in row.edges:
var rate: float = row.edges[destination]
if rate <= 0.0: continue
var share: float = moved * rate / outgoing
mass[destination] += share
occupation[destination] += years * share * 0.5
return {"mass": mass, "occupation": occupation}
func _solve_basis(generator: Dictionary, source: String, years: float) -> Dictionary:
var size: int = generator.rows.size()
var values: PackedFloat64Array = []
@@ -171,11 +201,11 @@ func _solve_basis(generator: Dictionary, source: String, years: float) -> Dictio
for node in range(e, size, 6): occupation[node] *= factor
return {"mass": mass, "occupation": occupation}
func _drift(target: Dictionary, rules: Dictionary, generator: Dictionary, years: float) -> void:
func _drift(target: Dictionary, rules: Dictionary, generator: Dictionary, years: float, physical: Dictionary = {}) -> void:
for id in rules:
if rules[id].ethics.is_empty() or not target.ethics.has(id): continue
var profile: Dictionary = State.normalize_profile(target.ethics[id])
var span: float = maxf(1e-12, E.approx(State.effective_span(world, target, id, rules)))
var span: float = maxf(1e-12, E.approx(State.effective_span(world, target, id, rules, physical)))
var duration: float = years / span
var pair_decay: float = exp(-0.03 * duration)
var opposite_decay: float = exp(-0.02 * duration)
@@ -306,55 +336,92 @@ func _limit(plans: Dictionary, budget: Dictionary, rules: Dictionary) -> void:
plans[source].counts[destination] = allowed
_merge(plans[source], source, rejected, plans[source].profiles[destination])
func _commit(target: Dictionary, physical: Dictionary, plans: Dictionary, rules: Dictionary, year: Variant) -> void:
## Apply one conserved source-by-owner table, then credit each destination once.
func _commit(target: Dictionary, physical: Dictionary, plans: Dictionary, rules: Dictionary, year: Variant) -> Dictionary:
var credits: Dictionary = {}
var contributions: Dictionary = {}
var births: Dictionary = {}
var ownership: Dictionary = {}
# Each source quota replaces its old physical population. Transfers between
# owners do not change these exact totals, and credits are applied below.
var final: Dictionary = physical.duplicate()
for source in plans:
if final.has(source): final[source] = plans[source].counts.get(source, 0)
var space_stages: Dictionary = {"Ct": true}
for id in rules: space_stages[id] = id == "Ct" or str(rules[id].home) != ""
for source in plans:
ownership[source] = {}
var free: Variant = target.amount.get(source, 0)
if E.gt(free, 0): ownership[source][""] = free
for owner in target.binds:
for source in target.binds[owner]:
if not ownership.has(source): continue
var held: Variant = target.binds[owner][source]
if E.gt(held, 0): ownership[source][owner] = held
for source in plans:
var plan: Dictionary = plans[source]
var slots: Dictionary = {"": target.amount.get(source, 0)}
for owner in target.binds:
var held: Variant = target.binds[owner].get(source, 0)
if E.gt(held, 0): slots[owner] = held
var remaining: Dictionary = {}
for destination in plan.counts:
var amount: Variant = plan.counts[destination]
if E.le(amount, 0): continue
remaining[destination] = amount
if not contributions.has(destination): contributions[destination] = []
contributions[destination].append({"amount": amount, "profile": plan.profiles[destination]})
var slots: Dictionary = ownership[source]
target.amount[source] = 0
var owners: Array = slots.keys()
if owners.size() == 1 and owners[0] == "":
target.amount[source] = remaining.get(source, 0)
for destination in remaining:
if destination == source: continue
var taken: Dictionary = Q.allocate_profiled(slots, amount)
for owner in taken:
if E.le(taken[owner], 0): continue
slots[owner] = E.subtract(slots[owner], taken[owner])
var host: String = owner
if State.is_space_civilization(destination, rules) or destination == "Ct": host = ""
elif owner == "" and State.is_space_civilization(source, rules): host = "@host"
var host: String = "@host" if space_stages.get(source, false) and not space_stages.get(destination, false) else ""
if not credits.has(destination): credits[destination] = {}
credits[destination][host] = E.add(credits[destination].get(host, 0), taken[owner])
target.amount[source] = slots[""]
for owner in slots:
if owner != "": target.binds[owner][source] = slots[owner]
credits[destination][host] = E.add(credits[destination].get(host, 0), remaining[destination])
continue
for i in owners.size():
var owner: String = owners[i]
# Last row receives the remaining exact quotas; the free-only path
# therefore needs no proportional allocation or repeated subtraction.
var allocation: Dictionary = remaining if i == owners.size() - 1 else Q.allocate_profiled(remaining, slots[owner])
for destination in allocation:
var amount: Variant = allocation[destination]
if E.le(amount, 0): continue
if i != owners.size() - 1:
remaining[destination] = E.subtract(remaining[destination], amount)
if destination == source:
if owner == "": target.amount[source] = amount
else: target.binds[owner][source] = amount
continue
var host: String = owner
if space_stages.get(destination, false): host = ""
elif owner == "" and space_stages.get(source, false): host = "@host"
if not credits.has(destination): credits[destination] = {}
credits[destination][host] = E.add(credits[destination].get(host, 0), amount)
if owner != "" and E.le(allocation.get(source, 0), 0): target.binds[owner][source] = 0
var hosts: Dictionary = {}
for id in physical:
if world.catalog.entities[id].event_group == "DwStar" and E.gt(physical[id], 0): hosts[id] = physical[id]
var host_shares: Array = _host_shares(hosts) if not hosts.is_empty() else []
for destination in credits:
var created: Variant = world.ledger.credit(target, destination, Q.sum(credits[destination]))
var requested: Variant = Q.sum(credits[destination])
var created: Variant = world.ledger.credit(target, destination, requested)
if final.has(destination): final[destination] = E.add(final[destination], created)
var bound: Variant = 0
for owner in credits[destination]:
var count: Variant = credits[destination][owner]
if owner == "" or E.le(count, 0): continue
if owner == "@host":
if hosts.is_empty(): continue
var ownership: Dictionary = Q.split_profile(target.civilization_remainders, "flow:hosts:" + destination, count, hosts.keys(), _host_shares(hosts))
for host in ownership:
if E.le(ownership[host], 0): continue
var distributed: Dictionary = Q.split_profile(target.civilization_remainders, "flow:hosts:" + destination, count, hosts.keys(), host_shares)
for host in distributed:
if E.le(distributed[host], 0): continue
if not target.binds.has(host): target.binds[host] = {}
target.binds[host][destination] = E.add(target.binds[host].get(destination, 0), ownership[host])
target.amount[destination] = E.subtract(target.amount[destination], count)
target.binds[host][destination] = E.add(target.binds[host].get(destination, 0), distributed[host])
else:
target.binds[owner][destination] = E.add(target.binds[owner].get(destination, 0), count)
target.amount[destination] = E.subtract(target.amount[destination], count)
bound = E.add(bound, count)
target.amount[destination] = E.subtract(target.amount[destination], bound)
if not rules.has(destination): births[destination] = created
var final: Dictionary = world.ledger.physical_amounts(target)
for destination in contributions:
if not rules.has(destination) or E.le(final.get(destination, 0), 0): continue
var profile: Dictionary = {}
@@ -369,6 +436,7 @@ func _commit(target: Dictionary, physical: Dictionary, plans: Dictionary, rules:
for ethic in ETHICS: profile[ethic] += entry.profile[ethic] * ratio
target.ethics[destination] = State.normalize_profile(profile)
for destination in births: world.batches.append_batch(target, destination, births[destination], year, year)
return final
func _host_shares(hosts: Dictionary) -> Array:
var total: Variant = Q.sum(hosts)
@@ -376,18 +444,18 @@ func _host_shares(hosts: Dictionary) -> Array:
for value in hosts.values(): result.append(E.ratio_approx(value, total))
return result
func _colonize(target: Dictionary, colonies: Dictionary, year: Variant, rules: Dictionary) -> void:
if colonies.is_empty(): return
var physical: Dictionary = world.ledger.physical_amounts(target)
func _colonize(target: Dictionary, colonies: Dictionary, year: Variant, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
if physical.is_empty(): physical = world.ledger.physical_amounts(target)
if colonies.is_empty(): return physical
var stocks: Dictionary = {}
for id in ["GG", "AB", "RP", "HP"]: stocks[id] = physical.get(id, 0)
var requests: Dictionary = {}
for id in colonies: requests[id] = colonies[id].counts.get(id, 0)
var granted: Dictionary = Q.allocate_profiled(requests, Q.sum(stocks))
var total: Variant = Q.sum(granted)
if E.le(total, 0): return
if E.le(total, 0): return physical
var taken_resources: Dictionary = Q.allocate_profiled(stocks, total)
var remaining: Dictionary = granted.duplicate()
var removed_by_owner: Dictionary = {}
for resource in taken_resources:
var removed: Variant = taken_resources[resource]
if E.le(removed, 0): continue
@@ -401,15 +469,32 @@ func _colonize(target: Dictionary, colonies: Dictionary, year: Variant, rules: D
if E.le(amount, 0): continue
if owner == "": target.amount[resource] = E.subtract(target.amount[resource], amount)
else: target.binds[owner][resource] = E.subtract(target.binds[owner][resource], amount)
var allocation: Dictionary = Q.allocate_profiled(remaining, amount)
removed_by_owner[owner] = E.add(removed_by_owner.get(owner, 0), amount)
var remaining: Dictionary = granted.duplicate()
var placements: Dictionary = {}
var final: Dictionary = physical.duplicate()
var owners: Array = removed_by_owner.keys()
for i in owners.size():
var owner: String = owners[i]
var allocation: Dictionary = remaining if i == owners.size() - 1 else Q.allocate_profiled(remaining, removed_by_owner[owner])
for destination in allocation:
var count: Variant = allocation[destination]
if E.le(count, 0): continue
remaining[destination] = E.subtract(remaining[destination], count)
if i != owners.size() - 1: remaining[destination] = E.subtract(remaining[destination], count)
if owner == "" or State.is_space_civilization(destination, rules): continue
if not placements.has(destination): placements[destination] = {}
placements[destination][owner] = count
for destination in granted:
var count: Variant = granted[destination]
if E.le(count, 0): continue
var created: Variant = world.ledger.credit(target, destination, count)
if owner != "" and not State.is_space_civilization(destination, rules):
target.amount[destination] = E.subtract(target.amount[destination], created)
target.binds[owner][destination] = E.add(target.binds[owner].get(destination, 0), created)
if final.has(destination): final[destination] = E.add(final[destination], created)
var bound: Variant = 0
for owner in placements.get(destination, {}):
var held: Variant = placements[destination][owner]
target.binds[owner][destination] = E.add(target.binds[owner].get(destination, 0), held)
bound = E.add(bound, held)
target.amount[destination] = E.subtract(target.amount[destination], bound)
for id in granted:
if E.le(granted[id], 0): continue
var profile: Dictionary = colonies[id].profiles[id]
@@ -420,6 +505,9 @@ func _colonize(target: Dictionary, colonies: Dictionary, year: Variant, rules: D
drifted[ethic] += profile[source] * (0.96 if source == ethic else (0.004 if Civ.OPPOSITE[source] == ethic else 0.009))
target.ethics[id] = State.merge_profiles(target.ethics.get(id, {}), physical.get(id, 0), drifted, granted[id])
target.civilization_stats.colonies = E.add(target.civilization_stats.get("colonies", 0), total)
for resource in taken_resources:
final[resource] = E.subtract(final.get(resource, 0), taken_resources[resource])
return final
func evolve(target: Dictionary, from_year: Variant, to_year: Variant, timer: RefCounted = null) -> void:
var keep_alive: RefCounted = self
@@ -430,12 +518,12 @@ func evolve(target: Dictionary, from_year: Variant, to_year: Variant, timer: Ref
# Bound the Poisson mean, processing the entire requested interval.
var maximum_years: float = years
var cursor: Variant = from_year
var physical: Dictionary = world.ledger.physical_amounts(target)
while E.lt(cursor, to_year):
if timer != null and not await timer.pause(): return
generator = _edges(target, rules)
generator = _edges(target, rules, physical)
maximum_years = 32.0 / generator.lambda if generator.lambda > 0.0 else years
var end: Variant = E.minimum(to_year, E.add(cursor, E.of(maximum_years)))
var physical: Dictionary = world.ledger.physical_amounts(target)
var started: int = world.profile_start()
var transaction: Dictionary = _plans(target, physical, rules, generator, E.approx(E.subtract(end, cursor)))
world.profile_end("civilization_flow_plan", started)
@@ -463,18 +551,18 @@ func evolve(target: Dictionary, from_year: Variant, to_year: Variant, timer: Ref
_limit(plans, State.stellar_budget(world, target, rules, physical), rules)
world.profile_end("civilization_flow_capacity", started)
started = world.profile_start()
_commit(target, physical, plans, rules, end)
physical = _commit(target, physical, plans, rules, end)
world.profile_end("civilization_flow_ledger", started)
if timer != null and not await timer.pause(): return
started = world.profile_start()
_colonize(target, transaction.colonies, end, rules)
physical = _colonize(target, transaction.colonies, end, rules, physical)
world.profile_end("civilization_flow_colonies", started)
started = world.profile_start()
_drift(target, rules, generator, E.approx(E.subtract(end, cursor)))
_drift(target, rules, generator, E.approx(E.subtract(end, cursor)), physical)
target.civilization_stats.wars = E.add(target.civilization_stats.get("wars", 0), transaction.wars)
target.civilization_stats.collateral = E.add(target.civilization_stats.get("collateral", 0), damage_total)
State.enforce_capacity(world, target, rules, end)
State.reconcile(world, target, rules)
target.civilization_stats.worship = Civ.worship(world, target, rules)
physical = State.enforce_capacity(world, target, rules, end, physical)
State.reconcile(world, target, rules, physical)
target.civilization_stats.worship = Civ.worship(world, target, rules, physical)
world.profile_end("civilization_flow_finish", started)
cursor = end
+10 -7
View File
@@ -133,10 +133,11 @@ 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:
static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year: Variant, physical: Dictionary = {}) -> Dictionary:
var profile_started: int = world.profile_start()
detach_space_civilizations(target, rules)
var physical: Dictionary = world.ledger.physical_amounts(target)
if physical.is_empty(): physical = world.ledger.physical_amounts(target)
else: physical = physical.duplicate()
reconcile(world, target, rules, physical)
var ethical_totals: Dictionary = physical.duplicate()
var budget: Dictionary = stellar_budget(world, target, rules, physical)
@@ -163,7 +164,9 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
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
world.ledger.credit(target, previous, excess)
var credited: Variant = world.ledger.credit(target, previous, excess)
physical[id] = E.maximum(0, supported)
physical[previous] = E.add(physical.get(previous, 0), credited)
world.batches.append_batch(target, previous, excess, year, year)
var previous_count: Variant = ethical_totals.get(previous, physical.get(previous, target.amount.get(previous, 0)))
var previous_profile: Dictionary = target.ethics.get(previous, empty_profile())
@@ -174,7 +177,7 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
# Capacity losses are proportional, so the surviving source keeps its profile.
if E.eq(previous, "C1.9"):
var stars: Dictionary = {}
var current_physical: Dictionary = world.ledger.physical_amounts(target)
var current_physical: Dictionary = physical
for star in current_physical:
if (E.eq(world.catalog.entities[star].event_group, "DwStar") and E.gt(current_physical[star], 0)):
stars[star] = current_physical[star]
@@ -192,10 +195,10 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
target.binds[owner][previous] = E.add(target.binds[owner].get(previous, 0), amount)
target.amount[previous] = E.subtract(target.amount[previous], amount)
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)
return physical
# Unity's structure chain: direct Claim resets the required structure count;
# intervening stages require ten times more units of the preceding structure.
@@ -247,13 +250,13 @@ static func assign_homes(world, target: Dictionary, rules: Dictionary, physical:
world.profile_end("assign_homes", profile_started)
static func effective_span(world, target: Dictionary, id: String, rules: Dictionary) -> Variant:
static func effective_span(world, target: Dictionary, id: String, rules: Dictionary, physical: 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
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))
var homes: Dictionary = stage_homes(world, target, id, world.catalog.civilization_layout(rules), physical)
target.civilization_homes[id] = homes
var multiplier: float = 1.0
for home in homes:
+26 -5
View File
@@ -5,13 +5,12 @@ const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
const QuantityMath = preload("res://world/owniverse/quantity_math.gd")
func initialize_life_batches() -> void:
func initialize_life_batches() -> Dictionary:
for id in Global.CivilizationRules:
world.state.batches.erase(id)
world.state.batch_origins.erase(id)
CivilizationState.detach_space_civilizations(world.state, Global.CivilizationRules)
CivilizationState.enforce_capacity(world, world.state, Global.CivilizationRules, world.state.time.datetime)
var physical: Dictionary = world.ledger.physical_amounts(world.state)
var physical: Dictionary = CivilizationState.enforce_capacity(world, world.state, Global.CivilizationRules, world.state.time.datetime)
for id in world.catalog.entities:
if world.catalog.entities[id].life_rule.is_empty():
continue
@@ -19,6 +18,7 @@ func initialize_life_batches() -> void:
for batch : Dictionary in world.state.batches.get(id, []):
tracked = E.add(tracked, batch.amount)
world.batches.append_batch(world.state, id, E.maximum(0, E.subtract(physical.get(id, 0), tracked)), world.state.time.datetime, world.state.time.datetime)
return physical
func run_frame(clock: Dictionary, budget: RefCounted) -> void:
@@ -28,13 +28,15 @@ func run_frame(clock: Dictionary, budget: RefCounted) -> void:
func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefCounted = null) -> void:
var _keep_alive: RefCounted = self
var adaptive_started: int = world.adaptive_timer_usec()
# Reconcile only when simulation advances.
# Fractional frame updates cannot change capacity or populations.
var processed_year: Variant = SimulationTime.processed_year(datetime)
var quantities: Dictionary = {}
if E.gt(processed_year, world.state.time.year):
if budget != null: budget.call("set_phase", "initialize_life_batches")
var profile_initialize: int = world.profile_start()
initialize_life_batches()
quantities = initialize_life_batches()
world.profile_end("initialize", profile_initialize)
var previous: Dictionary = world.state
if budget != null: budget.call("set_phase", "deep_copy_and_coarsen")
@@ -46,7 +48,6 @@ func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefC
var from_year: Variant = previous.time.year
var to_year: Variant = processed_year
if E.gt(to_year, from_year):
var quantities: Dictionary = world.ledger.physical_amounts(previous)
var distribution: Dictionary = QuantityMath.normalize_distribution(quantities)
next.distribution = distribution
@@ -71,12 +72,14 @@ func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefC
var profile_trends: int = world.profile_start()
WorldParameters.advance(previous, next, Global.CivilizationRules, E.subtract(to_year, from_year))
world.profile_end("trends", profile_trends)
world.apocalypse.check_density(next)
next.time = clock if not clock.is_empty() else {"datetime": datetime, "year": to_year}
# Publish only after every phase has finished; previous stays untouched.
world.state = E.bound_state(next)
if E.gt(to_year, from_year):
world.presentation.refresh(world.state, world.catalog)
world.update_adaptive_quality(world.adaptive_timer_usec() - adaptive_started)
func evolve_batches(target: Dictionary, to_year: Variant, budget: RefCounted = null) -> void:
@@ -88,7 +91,15 @@ func evolve_batches(target: Dictionary, to_year: Variant, budget: RefCounted = n
"enlightenment": WorldParameters.value(target, "Enlightenment"),
"explosiveness": WorldParameters.effective(target, "Exp")
}
var generations: int = 0
var generation_limit: int = 0
if world.adaptive_quality == 1: generation_limit = 3
elif world.adaptive_quality >= 2: generation_limit = 1
while await evolve_batch_generation(target, to_year, completed, budget, context):
generations += 1
# Direct terminal stellar events queued during a generation still run
# immediately, preserving their blast and matter outputs.
if generation_limit > 0 and generations >= generation_limit and completed.is_empty(): break
if budget != null and not await budget.pause(): return
@@ -108,6 +119,16 @@ func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Di
var batch_ids: Array = []
for id in target.batches:
if not Global.CivilizationRules.has(id): batch_ids.append(id)
if world.adaptive_quality >= 3:
batch_ids.sort()
var selected_ids: Array = []
for i in batch_ids.size():
if i % 2 == world.adaptive_generation_phase or completed.has(batch_ids[i]):
selected_ids.append(batch_ids[i])
for id in completed:
if not Global.CivilizationRules.has(id) and not selected_ids.has(id):
selected_ids.append(id)
batch_ids = selected_ids
var planets_changed: bool = false
var profile_withdraw: int = world.profile_start()
for id in batch_ids:
+78 -6
View File
@@ -3,7 +3,7 @@ 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) -> bool:
func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant, automatic: bool = false, physical: Dictionary = {}, helium_share_override: Variant = NAN) -> bool:
if (E.le(amount, 0) or E.ne(amount, E.floor_value(amount))):
return false
var trial: Dictionary = target.duplicate()
@@ -13,10 +13,25 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
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 physical: Dictionary = world.ledger.physical_amounts(trial)
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 = {}
var helium_share: Variant = stellar_helium_share(target) if not E.finite(helium_share_override) else helium_share_override
for resource in entity.cost:
var cost: Variant = E.round_value(E.multiply(E.maximum(0, entity.cost[resource]), amount))
if E.eq(resource, "H") and uses_stellar_helium(entity):
var helium_cost: Variant = E.minimum(E.floor_value(E.multiply(cost, helium_share)), world.ledger.available(trial, "He"))
cost = E.subtract(cost, helium_cost)
if E.gt(helium_cost, 0):
var helium_taken: Dictionary = world.ledger.take_available(trial, "He", helium_cost)
var total_helium_taken: Variant = 0
for id in helium_taken:
total_helium_taken = E.add(total_helium_taken, helium_taken[id])
if shared_physical and next_physical.has(id):
next_physical[id] = E.subtract(next_physical[id], helium_taken[id])
if E.ne(total_helium_taken, helium_cost):
return false
if (automatic and E.lt(trial.auto_resources.get(resource, 0), cost)):
return false
var taken: Dictionary = world.ledger.take_available(trial, resource, cost)
@@ -28,6 +43,7 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
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)
@@ -41,6 +57,7 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
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
@@ -95,6 +112,7 @@ func plan_smbh(desired_amount: Variant) -> Dictionary:
func produce_smbh(desired_amount: Variant) -> Variant:
if E.gt(world.state.get("apocalypse", {}).get("phase", 0), 0): return 0
var plan: Dictionary = plan_smbh(desired_amount)
var consumed_mass: Variant = plan.amount
var taken: Dictionary = plan.taken
@@ -120,10 +138,12 @@ func produce_smbh(desired_amount: Variant) -> Variant:
world.ledger.clamp_auto_resources(next)
world.state = next
world.presentation.refresh(world.state, world.catalog)
world.apocalypse.check_density(world.state)
return consumed_mass
func produce(item_id: String, desired_amount: Variant) -> Variant:
if E.gt(world.state.get("apocalypse", {}).get("phase", 0), 0): return 0
if E.eq(item_id, "SMBH"):
return produce_smbh(desired_amount)
# Use special production for S and H
@@ -143,19 +163,23 @@ func produce(item_id: String, desired_amount: Variant) -> Variant:
return 0
var metallicity: Variant = world.environment.stellar_environment(world.state).metallicity
if (not try_production(world.state, entity, max_producible)):
var helium_share: Variant = stellar_helium_share(world.state)
if (not try_production(world.state, entity, max_producible, false, {}, helium_share)):
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)
var lifespan_multiplier: Variant = stellar_lifespan_multiplier(entity, helium_share)
world.batches.append_batch(world.state, item_id, max_producible, world.state.time.datetime, world.state.time.datetime, {}, -INF, null, null, NAN, lifespan_multiplier)
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)
world.apocalypse.check_density(world.state)
return max_producible
func produce_hs(entity_id: String, value: Variant) -> Variant:
if E.gt(world.state.get("apocalypse", {}).get("phase", 0), 0): return 0
if E.le(value, 0):
return 0
@@ -167,7 +191,8 @@ func produce_hs(entity_id: String, value: Variant) -> Variant:
world.state.energy = E.subtract(world.state.energy, to_produce)
world.ledger.process_entity(entity_id, to_produce)
world.presentation.refresh(world.state, world.catalog)
world.apocalypse.check_density(world.state)
return to_produce
@@ -175,6 +200,39 @@ func production_step(entity: OwniverseEntity) -> Variant:
return QuantityMath.production_step(E.add(entity.cost.values(), entity.bind.values()))
func uses_stellar_helium(entity: OwniverseEntity) -> bool:
return E.eq(entity.group_as, "Star") and entity.cost.has("H")
## Unity's Hel state is the free helium share of the H+He mixture, capped at 50%.
func stellar_helium_share(source: Dictionary) -> Variant:
var hydrogen: Variant = world.ledger.available(source, "H")
var helium: Variant = world.ledger.available(source, "He")
var total: Variant = E.add(hydrogen, helium)
if E.le(total, 0): return 0
var parameters: Dictionary = Global.ExtraParams
var share: Variant = E.multiply(E.divide(helium, total), parameters.get("helium_multiplier", 1))
share = E.minimum(share, E.of("0.5"))
share = E.minimum(share, E.maximum(0, parameters.get("helium_bonus_limit", 1)))
return E.clamp_value(share, 0, 1)
func stellar_lifespan_multiplier(entity: OwniverseEntity, helium_share: Variant) -> Variant:
return E.maximum(0, E.subtract(1, E.multiply(E.of("1.5"), helium_share))) if uses_stellar_helium(entity) else 1
## Maximum star count when helium may replace this share of its hydrogen cost.
func stellar_hydrogen_capacity(hydrogen_cost: Variant, hydrogen_available: Variant, helium_available: Variant, helium_share: Variant) -> Variant:
if E.le(hydrogen_cost, 0): return INF
if E.le(helium_share, 0) or E.le(helium_available, 0):
return E.floor_value(E.divide(hydrogen_available, hydrogen_cost))
var hydrogen_only_capacity: Variant = E.divide(hydrogen_available, E.multiply(hydrogen_cost, E.subtract(1, helium_share)))
var helium_saturation_capacity: Variant = E.divide(helium_available, E.multiply(hydrogen_cost, helium_share))
if E.le(hydrogen_only_capacity, helium_saturation_capacity):
return E.floor_value(hydrogen_only_capacity)
return E.floor_value(E.divide(E.add(hydrogen_available, helium_available), hydrogen_cost))
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
@@ -192,10 +250,24 @@ func calculate_max_producible(entity: OwniverseEntity, desired_amount: Variant =
if (not has_requirements):
return 0
var available: Variant = INF if desired_amount == null else E.maximum(0, desired_amount)
var helium_share: Variant = stellar_helium_share(world.state)
for resource in requirements:
if E.eq(resource, "H") and uses_stellar_helium(entity):
var hydrogen_cost: Variant = E.maximum(0, entity.cost.get("H", 0))
available = E.minimum(available, stellar_hydrogen_capacity(hydrogen_cost, world.ledger.available(world.state, "H"), world.ledger.available(world.state, "He"), helium_share))
var hydrogen_binding: Variant = E.maximum(0, entity.bind.get("H", 0))
if E.gt(hydrogen_binding, 0):
available = E.minimum(available, E.floor_value(E.divide(world.ledger.available(world.state, "H"), hydrogen_binding)))
continue
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)
var concrete_requirements: Dictionary = requirements.duplicate()
if uses_stellar_helium(entity):
# The direct H cost was already handled by the H/He capacity above;
# preserve any separate H binding requirement.
concrete_requirements["H"] = E.maximum(0, E.subtract(concrete_requirements.get("H", 0), entity.cost.get("H", 0)))
if E.le(concrete_requirements["H"], 0): concrete_requirements.erase("H")
var concrete: Dictionary = world.ledger.expand_requirements(world.state, concrete_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])))
+9 -7
View File
@@ -6,13 +6,11 @@ const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
## Diagnostic switch for paired measurements of the old and new timing models.
var cycle_stars: bool = true
var _cycle_widths: Dictionary = {}
func star_cycle_width(target: Dictionary) -> Variant:
var speed: int = int(target.get("speed", 1))
if not _cycle_widths.has(speed):
_cycle_widths[speed] = E.multiply(E.power(10, speed - 1), world.frame_step_seconds)
return _cycle_widths[speed]
var width: Variant = E.multiply(E.power(10, speed - 1), world.frame_step_seconds)
return E.multiply(width, E.power(2, world.adaptive_quality))
## The cycle grid may widen a short window, never subdivide a wide one.
func uses_cycle_star(target: Dictionary, id: String, lifespan: Variant) -> bool:
@@ -70,7 +68,11 @@ func coarsen_cycle_stars(target: Dictionary, id: String) -> void:
func temporal_resolution(target: Dictionary) -> Variant:
var speed: int = int(target.get("speed", 1))
return E.divide(E.power(10, speed - 1), 10) if speed >= 4 else 0
if speed < 4: return 0
# Keep the existing 0.1-second resolution at the default 5 Hz cadence,
# and scale temporal grouping with the adaptive cycle interval.
var width: Variant = E.multiply(E.power(10, speed - 1), world.frame_step_seconds / 2.0)
return E.multiply(width, E.power(2, world.adaptive_quality))
func window_duration(target: Dictionary, lifespan: Variant) -> Variant:
if E.le(lifespan, 10): return lifespan
@@ -256,7 +258,7 @@ func scale_batches(target: Dictionary, id: String, factor: Variant) -> void:
share.batch.withdraw_remainder = E.minimum(share.batch.get("withdraw_remainder", 0), share.batch.amount)
func append_batch(target: Dictionary, id: String, amount: Variant, created_from: Variant, created_to: Variant, completed: Dictionary = { }, completed_before: Variant = - INF, budget: RefCounted = null, newborn_adjustment: Variant = null, newborn_until: Variant = NAN) -> Variant:
func append_batch(target: Dictionary, id: String, amount: Variant, created_from: Variant, created_to: Variant, completed: Dictionary = { }, completed_before: Variant = - INF, budget: RefCounted = null, newborn_adjustment: Variant = null, newborn_until: Variant = NAN, lifespan_multiplier: Variant = 1) -> Variant:
var _keep_alive: RefCounted = self
if Global.CivilizationRules.has(id): return NAN
if ((E.le(amount, 0) or (not world.catalog.entities.has(id))) or E.le(world.catalog.entities[id].lifespan, 0)):
@@ -269,7 +271,7 @@ func append_batch(target: Dictionary, id: String, amount: Variant, created_from:
if (not target.has("batch_origins")):
target.batch_origins = {}
var batches: Array = target.batches[id]
var lifespan: Variant = effective_lifespan(target, id)
var lifespan: Variant = E.multiply(effective_lifespan(target, id), E.maximum(0, lifespan_multiplier))
if uses_cycle_star(target, id, lifespan):
return await append_cycle_star(target, id, amount, created_from, created_to, lifespan, completed, completed_before, budget, newborn_adjustment, newborn_until)
var batch_duration: Variant = window_duration(target, lifespan)
+31 -19
View File
@@ -63,29 +63,41 @@ func expand_requirements(source: Dictionary, requirements: Dictionary) -> Dictio
func take_available(target: Dictionary, id: String, amount: Variant) -> Dictionary:
var quantities: Dictionary = {}
for member in world.catalog.members(id):
quantities[member] = target.amount.get(member, 0)
var distribution: Dictionary = QuantityMath.normalize_distribution(quantities)
quantities[member] = E.maximum(0, target.amount.get(member, 0))
var taken: Dictionary = {}
var total: Variant = E.minimum(amount, available(target, id))
var discrete: bool = (not distribution.is_empty())
for member in distribution:
discrete = (discrete and is_discrete(member))
if discrete:
total = E.floor_value(total)
var stock_total: Variant = 0
for quantity in quantities.values(): stock_total = E.add(stock_total, quantity)
var total: Variant = E.minimum(amount, stock_total)
var discrete: bool = true
for member in quantities:
discrete = discrete and is_discrete(member)
if discrete: total = E.floor_value(total)
if E.le(total, 0) or E.le(stock_total, 0): return taken
if E.eq(total, stock_total):
taken = quantities
elif discrete:
var shares: Array = []
var assigned: Variant = 0
for member in quantities:
var expected: Variant = E.mul_div(total, quantities[member], stock_total)
var whole: Variant = E.floor_value(expected)
taken[member] = whole
assigned = E.add(assigned, whole)
shares.append({"id": member, "rest": E.subtract(expected, whole)})
shares.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return E.gt(a.rest, b.rest) if E.ne(a.rest, b.rest) else E.lt(str(a.id), str(b.id)))
var missing: Variant = E.subtract(total, assigned)
for share: Dictionary in shares:
if E.le(missing, 0): break
var member: String = share.id
if E.lt(taken[member], quantities[member]):
taken[member] = E.add(taken[member], 1)
missing = E.subtract(missing, 1)
assert(E.eq(missing, 0), "Discrete resource allocation must exhaust its requested amount")
else:
var distribution: Dictionary = QuantityMath.normalize_distribution(quantities)
for member in distribution:
var expected: Variant = E.multiply(total, distribution[member])
taken[member] = (E.floor_value(expected) if discrete else expected)
assigned = E.add(assigned, taken[member])
shares.append({"id": member, "rest": E.subtract(expected, taken[member])})
if discrete:
shares.sort_custom(func ( a : Dictionary , b : Dictionary ) -> bool : return (E.gt(a.rest, b.rest) if E.ne(a.rest, b.rest) else E.lt(str(a.id), str(b.id))))
for share : Dictionary in shares:
if E.ge(assigned, total):
break
taken[share.id] = E.add(taken[share.id], 1)
assigned = E.add(assigned, 1)
taken[member] = E.multiply(total, distribution[member])
for member in taken:
target.amount[member] = E.maximum(0, E.subtract(target.amount.get(member, 0), taken[member]))
return taken
+120 -18
View File
@@ -1,5 +1,6 @@
extends Node
class_name Owniverse
signal production_applied(id: String)
const E = preload("res://world/owniverse/math/exact.gd")
## Owns the state and modules. Rules live in the modules below.
const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
@@ -11,12 +12,66 @@ var ledger = preload("res://world/owniverse/object_ledger.gd").new(self)
var production = preload("res://world/owniverse/manual_production.gd").new(self)
var automatic = preload("res://world/owniverse/automatic_production.gd").new(self)
var stellar = preload("res://world/owniverse/stellar_rules.gd").new(self)
var apocalypse = preload("res://world/owniverse/apocalypse_controller.gd").new(self)
var life = preload("res://world/owniverse/life_evolution.gd").new(self)
var environment = preload("res://world/owniverse/universe_environment.gd").new(self)
var evolution = preload("res://world/owniverse/evolution_coordinator.gd").new(self)
var civilization_flow = preload("res://world/owniverse/civilization_flow.gd").new(self)
var profile_enabled: bool = false
var profile: Dictionary = {}
## Runtime-only quality controller. It is deliberately not serialized.
var adaptive_quality: int = 0
var adaptive_cycle_ema_ms: float = 0.0
var adaptive_recovery_cycles: int = 0
var adaptive_generation_phase: int = 0
## Runtime-only cadence: 5, 4, 3, or 2 full simulation cycles per second.
var adaptive_cadence: bool = true
var cadence_level: int = 0
var cadence_recovery_cycles: int = 0
const CADENCE_STEPS: Array[float] = [0.2, 0.25, 1.0 / 3.0, 0.5]
var frame_step_seconds: float = 0.2
func update_adaptive_quality(cycle_usec: int) -> void:
var cycle_ms: float = float(cycle_usec) / 1000.0
adaptive_cycle_ema_ms = cycle_ms if adaptive_cycle_ema_ms <= 0.0 else adaptive_cycle_ema_ms * 0.8 + cycle_ms * 0.2
if adaptive_cycle_ema_ms > 300.0:
adaptive_quality = 3
adaptive_recovery_cycles = 0
elif adaptive_quality >= 2 and adaptive_cycle_ema_ms > 200.0:
adaptive_quality = 3
adaptive_recovery_cycles = 0
elif adaptive_cycle_ema_ms > 220.0:
adaptive_quality = maxi(adaptive_quality, 2)
adaptive_recovery_cycles = 0
elif adaptive_cycle_ema_ms > 200.0:
adaptive_quality = maxi(adaptive_quality, 1)
adaptive_recovery_cycles = 0
elif adaptive_cycle_ema_ms < 140.0:
adaptive_recovery_cycles += 1
if adaptive_recovery_cycles >= 30:
adaptive_quality = maxi(0, adaptive_quality - 1)
adaptive_recovery_cycles = 0
else:
adaptive_recovery_cycles = 0
if adaptive_cadence:
var step: float = CADENCE_STEPS[cadence_level]
if adaptive_cycle_ema_ms > step * 900.0 and cadence_level < CADENCE_STEPS.size() - 1:
cadence_level += 1
frame_step_seconds = CADENCE_STEPS[cadence_level]
cadence_recovery_cycles = 0
elif adaptive_cycle_ema_ms < step * 650.0 and cadence_level > 0:
cadence_recovery_cycles += 1
if cadence_recovery_cycles >= 30:
cadence_level -= 1
frame_step_seconds = CADENCE_STEPS[cadence_level]
cadence_recovery_cycles = 0
else:
cadence_recovery_cycles = 0
if adaptive_quality >= 3:
adaptive_generation_phase = 1 - adaptive_generation_phase
func adaptive_timer_usec() -> int:
return Time.get_ticks_usec() - (frame_budget.waiting_usec if frame_busy and frame_budget != null else 0)
func profile_start() -> int:
if not profile_enabled: return 0
@@ -55,6 +110,7 @@ var state: Dictionary = {
'civilization_stats': {},
'civilization_homes': {},
'ethics': {},
'apocalypse': {'phase': 0, 'type': 0, 'warning': 0, 'stage': 1, 'tick': 0, 'end_ticks': 0},
'stats': {'total_amount': {}},
}
@@ -64,42 +120,86 @@ var frame_clock: Dictionary = {}
var frame_budget: RefCounted
var _requested_speed: int = 0
var _queued_productions: Array = []
var production_queue_busy: bool = false
var _production_queue_cancelled: bool = false
var frame_active_clock: Dictionary = {}
var _resume_clock: Dictionary = {}
const frame_step_seconds: float = 0.2
var frame_elapsed_seconds: float = 0.0
const production_slice_usec: int = 4000
## The HUD follows requested time; world state remains at the last committed cycle.
func display_clock() -> Dictionary:
return frame_clock if not frame_clock.is_empty() else state.time
## Keep the requested clock advancing while a transaction spans render frames.
func evolute_frame(delta: float, tree: SceneTree) -> void:
if not is_finite(delta) or delta < 0.0 or not is_finite(delta * pow(10.0, speed - 1)): return
if E.ge(state.get("apocalypse", {}).get("phase", 0), 2):
frame_clock = state.time.duplicate()
frame_elapsed_seconds = 0.0
return
if frame_clock.is_empty(): frame_clock = state.time.duplicate()
frame_clock = SimulationTime.advance(frame_clock, delta, speed)
if frame_busy: return
frame_elapsed_seconds += delta
if frame_busy or production_queue_busy: return
var next_clock: Dictionary = _resume_clock
if next_clock.is_empty() and not _queued_productions.is_empty():
_start_production_drain(tree)
return
if next_clock.is_empty():
next_clock = SimulationTime.advance(state.time, frame_step_seconds, speed)
# Start only after a full 0.2 seconds of simulation time is requested.
if E.gt(next_clock.datetime, frame_clock.datetime): return
# Cadence limits starts; each transaction consumes the whole pending interval.
if frame_elapsed_seconds + 1e-12 < frame_step_seconds: return
next_clock = frame_clock.duplicate()
frame_elapsed_seconds = 0.0
frame_busy = true
frame_budget = preload("res://world/owniverse/frame_budget.gd").new(tree)
frame_active_clock = next_clock
_resume_clock = {}
evolution.run_frame(frame_active_clock, frame_budget)
func request_production(id: String, amount: Variant, resource: bool = false) -> void:
if frame_busy:
func request_production(id: String, amount: Variant, resource: bool = false) -> bool:
if E.gt(state.get("apocalypse", {}).get("phase", 0), 0): return false
if frame_busy or production_queue_busy:
_queued_productions.append({"id": id, "amount": amount, "resource": resource})
elif resource:
production.produce_hs(id, amount)
return true
_apply_production({"id": id, "amount": amount, "resource": resource})
return false
func _apply_production(command: Dictionary) -> void:
if command.resource:
production.produce_hs(command.id, command.amount)
else:
production.produce(id, amount)
production.produce(command.id, command.amount)
production_applied.emit(command.id)
func _start_production_drain(tree: SceneTree) -> void:
if production_queue_busy or _queued_productions.is_empty(): return
production_queue_busy = true
_production_queue_cancelled = false
_drain_productions(tree)
func _drain_productions(tree: SceneTree) -> void:
var keep_alive: Owniverse = self
while not _queued_productions.is_empty():
var slice_started: int = Time.get_ticks_usec()
while not _queued_productions.is_empty() and Time.get_ticks_usec() - slice_started < production_slice_usec:
var command: Dictionary = _queued_productions.pop_front()
_apply_production(command)
if _queued_productions.is_empty(): break
await tree.process_frame
if keep_alive._production_queue_cancelled: return
production_queue_busy = false
## Persist work requests, never the partially calculated transaction.
func frame_checkpoint() -> Dictionary:
return {"requested_clock": frame_clock, "active_clock": frame_active_clock if frame_busy else {}, "speed": speed, "productions": _queued_productions}
return {"requested_clock": frame_clock, "active_clock": frame_active_clock if frame_busy else {}, "speed": speed, "productions": _queued_productions, "elapsed_seconds": frame_elapsed_seconds}
func restore_frame_checkpoint(checkpoint: Dictionary) -> void:
frame_clock = checkpoint.get("requested_clock", {})
_resume_clock = checkpoint.get("active_clock", {})
var reserved_clock: Dictionary = _resume_clock if not _resume_clock.is_empty() else state.time
var pending_years: Variant = E.maximum(0, E.subtract(frame_clock.get("datetime", reserved_clock.datetime), reserved_clock.datetime))
frame_elapsed_seconds = float(checkpoint.get("elapsed_seconds", E.approx(pending_years) / pow(10.0, int(checkpoint.get("speed", state.speed)) - 1)))
_queued_productions = checkpoint.get("productions", [])
if _resume_clock.is_empty():
speed = checkpoint.get("speed", state.speed)
@@ -112,23 +212,25 @@ func finish_frame() -> void:
if _requested_speed != 0:
speed = _requested_speed
_requested_speed = 0
var productions: Array = _queued_productions
_queued_productions = []
for command in productions:
request_production(command.id, command.amount, command.resource)
if not _queued_productions.is_empty():
_start_production_drain(frame_budget.tree)
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and frame_budget != null:
if what == NOTIFICATION_PREDELETE:
_production_queue_cancelled = true
if frame_budget != null:
frame_budget.cancelled = true
var speed: int :
get:
return _requested_speed if _requested_speed != 0 else state.speed
set(value):
var requested_speed: int = clampi(value, 1, Global.SPEED_LIMIT)
if E.eq(state.get("apocalypse", {}).get("phase", 0), 1): requested_speed = 1
if frame_busy:
_requested_speed = clampi(value, 1, Global.SPEED_LIMIT)
_requested_speed = requested_speed
return
state.speed = clampi(value, 1, Global.SPEED_LIMIT)
state.speed = requested_speed
state.speed_in_year = E.power(10, E.subtract(state.speed, 1))
func evolute(delta: Variant) -> void:
+21
View File
@@ -303,6 +303,27 @@ static func split_arrays(remainders: Dictionary, key: String, count: Variant, id
static func accumulate_rate(remainders: Dictionary, key: String, count: Variant, probability: Variant) -> Variant:
# Reward rates are floats while populations are exact whole values. Keep
# the product as quotient plus fractional carry instead of constructing an
# intermediate Exact Value and flooring it again.
if probability is float and is_finite(probability) and probability >= 0.0 and (count is int or (count is E.Value and count.fraction == 0.0)) and E.ge(count, 0):
var terms: Array = E.parts(probability)
if terms[2] > 0:
var population: Array = E._blocks(count)[0]
var whole_product: Array = E.Big.multiply(population, terms[0])
var qr: Array = E.Big.divmod(whole_product, terms[1])
var fractional: float = E._fraction_ratio(qr[1], terms[1])
var carry: float = E.approx(remainders.get(key, 0))
if carry >= 0.0 and carry < 1.0:
if qr[1] == [0]:
remainders[key] = carry
return E._compose(qr[0])
fractional += carry
if fractional > 0.5 and absf(fractional - roundf(fractional)) <= 1e-12:
fractional = roundf(fractional)
var extra: int = floori(fractional)
remainders[key] = fractional - extra
return E.add(E._compose(qr[0]), extra)
var product: Variant = E.multiply(count, E.maximum(0, probability))
var carry: float = E.approx(remainders.get(key, 0))
if carry >= 0.0 and carry < 1.0 and E.ge(product, 0):
+2 -2
View File
@@ -2,8 +2,8 @@ extends "res://world/owniverse/simulation_module.gd"
const E = preload("res://world/owniverse/math/exact.gd")
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
func stellar_environment(source: Dictionary) -> Dictionary:
var quantities: Dictionary = world.ledger.physical_amounts(source)
func stellar_environment(source: Dictionary, quantities: Dictionary = {}) -> Dictionary:
if quantities.is_empty(): quantities = world.ledger.physical_amounts(source)
# SMBH is a self-group in GI; its quantity stores mass, not object count.
var mass: Variant = source.amount.get("SMBH", 0)
# Geometric volume includes a 1e22 scale per void and can exceed E27.
+36 -8
View File
@@ -64,12 +64,15 @@
[ext_resource type="Texture2D" uid="uid://bce2bpw5642er" path="res://ui/achievements/achievements.en.png" id="61_d36xh"]
[ext_resource type="Script" uid="uid://vy0dj2cqfmk0" path="res://ui/credits/credits.gd" id="62_o2i0n"]
[ext_resource type="Texture2D" uid="uid://c46h78ewwlafk" path="res://ui/credits/credits.en.png" id="62_vu83j"]
[ext_resource type="PackedScene" uid="uid://cs8a5u3ecf51t" path="res://ui/achievements/achievement_item.tscn" id="64_0drd1"]
[ext_resource type="Texture2D" uid="uid://drvvkd724g8hy" path="res://ui/credits/kid_gp_light.png" id="64_rur7c"]
[ext_resource type="Texture2D" uid="uid://b33ji4mpyw4ks" path="res://ui/credits/kid_gp.png" id="65_y1rwn"]
[ext_resource type="Texture2D" uid="uid://dgt52qiyuirso" path="res://ui/indicators/density/warning_border.png" id="66_rur7c"]
[ext_resource type="Script" uid="uid://btsrt6rw3nr04" path="res://ui/audio/owniverse_music_player.gd" id="67_music"]
[ext_resource type="Script" uid="uid://botb0k4qr0hqo" path="res://ui/audio/owniverse_effects_player.gd" id="68_effects"]
[ext_resource type="Script" path="res://ui/report/report.gd" id="69_report"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ifjjt"]
[sub_resource type="ButtonGroup" id="ButtonGroup_58wjt"]
@@ -234,23 +237,23 @@ theme_override_constants/separation = 0
layout_mode = 2
theme_override_constants/separation = 0
[node name="PageHeader" type="HBoxContainer" parent="Stage/World/PageScroller/HBoxContainer/ForcePage" unique_id=2102307933]
[node name="ForceHeader" type="HBoxContainer" parent="Stage/World/PageScroller/HBoxContainer/ForcePage" unique_id=2102307933]
custom_minimum_size = Vector2(0, 128)
layout_mode = 2
size_flags_horizontal = 6
size_flags_vertical = 4
theme_override_constants/separation = 0
[node name="LeftIndicator" type="TextureButton" parent="Stage/World/PageScroller/HBoxContainer/ForcePage/PageHeader" unique_id=663302862]
[node name="LeftIndicator" type="TextureButton" parent="Stage/World/PageScroller/HBoxContainer/ForcePage/ForceHeader" unique_id=663302862]
custom_minimum_size = Vector2(316, 64)
layout_mode = 2
[node name="Header" type="TextureRect" parent="Stage/World/PageScroller/HBoxContainer/ForcePage/PageHeader" unique_id=1295272502]
[node name="Header" type="TextureRect" parent="Stage/World/PageScroller/HBoxContainer/ForcePage/ForceHeader" unique_id=1295272502]
custom_minimum_size = Vector2(440, 64)
layout_mode = 2
texture = ExtResource("13_dp8ul")
[node name="RighIndicator" type="TextureButton" parent="Stage/World/PageScroller/HBoxContainer/ForcePage/PageHeader" unique_id=504419468]
[node name="RighIndicator" type="TextureButton" parent="Stage/World/PageScroller/HBoxContainer/ForcePage/ForceHeader" unique_id=504419468]
custom_minimum_size = Vector2(316, 64)
layout_mode = 2
@@ -1011,7 +1014,8 @@ layout_mode = 2
texture_normal = ExtResource("52_uvuuk")
[node name="Report" type="VBoxContainer" parent="Stage" unique_id=1422399839]
visible = false
script = ExtResource("69_report")
custom_minimum_size = Vector2(1080, 1920)
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
@@ -1032,17 +1036,26 @@ custom_minimum_size = Vector2(1080, 0)
layout_mode = 2
[node name="Header" type="TextureRect" parent="Stage/Report/Header" unique_id=1321780840]
custom_minimum_size = Vector2(824, 256)
layout_mode = 2
texture = ExtResource("60_an6vj")
[node name="ReportBody" type="RichTextLabel" parent="Stage/Report" unique_id=1952288631]
layout_mode = 2
size_flags_vertical = 3
bbcode_enabled = true
scroll_active = true
theme_override_font_sizes/normal_font_size = 30
theme_override_font_sizes/bold_font_size = 30
[node name="Achievements" type="VBoxContainer" parent="Stage" unique_id=1847732432]
visible = false
custom_minimum_size = Vector2(1080, 1920)
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="TopMenu" type="HBoxContainer" parent="Stage/Achievements" unique_id=40287683]
custom_minimum_size = Vector2(0, 128)
layout_mode = 2
[node name="BackButton" type="TextureButton" parent="Stage/Achievements/TopMenu" unique_id=1719885364]
@@ -1054,18 +1067,31 @@ texture_normal = ExtResource("44_uvuuk")
texture_pressed = ExtResource("45_an6vj")
[node name="Header" type="CenterContainer" parent="Stage/Achievements" unique_id=51521239]
custom_minimum_size = Vector2(1080, 0)
layout_mode = 2
[node name="Title" type="TextureRect" parent="Stage/Achievements/Header" unique_id=1195638572]
custom_minimum_size = Vector2(824, 256)
custom_minimum_size = Vector2(824, 128)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
texture = ExtResource("61_d36xh")
stretch_mode = 3
[node name="AchievementList" type="ScrollContainer" parent="Stage/Achievements" unique_id=1872434750]
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="AchievementItems" type="VBoxContainer" parent="Stage/Achievements/AchievementList" unique_id=1878761996]
layout_mode = 2
size_flags_horizontal = 3
[node name="AchievementItem" parent="Stage/Achievements/AchievementList/AchievementItems" unique_id=1952626354 instance=ExtResource("64_0drd1")]
visible = false
layout_mode = 2
[node name="Credits" type="VBoxContainer" parent="Stage" unique_id=785042592]
visible = false
layout_mode = 0
offset_right = 1080.0
offset_bottom = 1920.0
@@ -1141,6 +1167,8 @@ script = ExtResource("67_music")
[connection signal="pressed" from="Stage/MainMenu/TopMenu/BackButton" to="Stage" method="_on_topmenu_button_pressed" binds= ["World"]]
[connection signal="pressed" from="Stage/MainMenu/CreateOwniverse" to="Stage" method="create_new_universe"]
[connection signal="pressed" from="Stage/MainMenu/Achievements" to="Stage" method="_on_topmenu_button_pressed" binds= ["Achievements"]]
[connection signal="pressed" from="Stage/MainMenu/Language/English" to="Stage/MainMenu" method="_on_change_locale_pressed" binds= ["en"]]
[connection signal="pressed" from="Stage/MainMenu/Language/Russian" to="Stage/MainMenu" method="_on_change_locale_pressed" binds= ["ru"]]
[connection signal="pressed" from="Stage/MainMenu/Media/Sound" to="Stage/MainMenu" method="_on_sound_pressed"]
[connection signal="pressed" from="Stage/MainMenu/Media/Music" to="Stage/MainMenu" method="_on_music_pressed"]
[connection signal="value_changed" from="Stage/MainMenu/Volume/SoundVolume" to="Stage/MainMenu" method="_on_sound_volume_value_changed"]