Add comprehensive tests and optimizations for simulation and UI performance
- Introduced a new benchmark save script to create a stress test scenario with extreme values for simulation performance. - Added a full UI benchmark script to measure frame rates and performance metrics during UI interactions. - Implemented tests for coarse generations to ensure population transitions are handled correctly. - Created tests for extra parameters to validate the loading and application of configuration settings. - Developed simulation optimization tests to verify the efficiency of population drift and rounding mechanisms. - Added split optimization tests to ensure accurate allocation and rounding behavior in population management. - Included startup parameter tests to confirm the correct initialization of global parameters before scene readiness. - Enhanced the audit rounding ledger to compare selective and full rounding results for consistency.
This commit is contained in:
+20
-3
@@ -2,6 +2,7 @@ extends Node
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
const SPEED_LIMIT: int = 6
|
||||
const SUPPORTED_LOCALES: PackedStringArray = ["en", "ru"]
|
||||
|
||||
# Write a readable diagnostic copy next to the encrypted save. Never used for loading.
|
||||
var save_plaintext_copy: bool = true
|
||||
@@ -15,16 +16,20 @@ var Settings: Dictionary = {
|
||||
"language": "en",
|
||||
}
|
||||
|
||||
# TODO getter setter for locale
|
||||
|
||||
#var Locales: Dictionary = {}
|
||||
var Locale = "en"
|
||||
var _locale: String = "en"
|
||||
var Locale: String:
|
||||
get:
|
||||
return _locale
|
||||
set(value):
|
||||
set_locale(value)
|
||||
|
||||
var OwniverseEntities: Dictionary = {}
|
||||
var OwniverseItems: Dictionary = {}
|
||||
var Templates: Dictionary = {}
|
||||
var LifeRules: Dictionary = {}
|
||||
var CivilizationRules: Dictionary = {}
|
||||
var ExtraParams: Dictionary = {}
|
||||
|
||||
var Music: Dictionary = {}
|
||||
var Effects: Dictionary = {}
|
||||
@@ -32,6 +37,12 @@ var Effects: Dictionary = {}
|
||||
var batch_count: float = 40.0 # must be even not odd
|
||||
|
||||
|
||||
func set_locale(locale: String) -> void:
|
||||
_locale = locale if SUPPORTED_LOCALES.has(locale) else "en"
|
||||
Settings.language = _locale
|
||||
TranslationServer.set_locale(_locale)
|
||||
|
||||
|
||||
#func get_localized_item_description(item_id: String, item: String) -> String:
|
||||
#if Locales[Locale].has(item_id):
|
||||
#return Locales[Locale][item_id][item]
|
||||
@@ -68,7 +79,13 @@ func format_number(value, digits: int = 0) -> String:
|
||||
return E.grouped(value, digits)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Stage constructs its world before autoload _ready callbacks run.
|
||||
ExtraParams = preload("res://data/owniverse_extra_param_loader.gd").get_data()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_locale(str(Settings.language))
|
||||
|
||||
LifeRules = OwniverseLifeRuleLoader.get_data()
|
||||
OwniverseEntities = OwniverseEntityLoader.get_data()
|
||||
|
||||
@@ -374,8 +374,10 @@ func load_settings():
|
||||
var config = ConfigFile.new()
|
||||
var err = config.load(SAVE_PATH)
|
||||
if E.ne(err, OK):
|
||||
Global.set_locale(str(Global.Settings.language))
|
||||
return false
|
||||
for item in Global.Settings:
|
||||
Global.Settings[item] = config.get_value(SETTING_SECTION, item, Global.Settings[item])
|
||||
Global.set_locale(str(Global.Settings.language))
|
||||
return true
|
||||
#endregion
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
Civ|Stars|Home|Fall|ScaleLevel|Description|EthL|Lev|Eth|Wor|Pro|Deg|Col|War|Dip|WorC|ProC|DegC|ColC|WarC|DipC|Span|Ret
|
||||
RP|||||Каменистая планета. Все умерли.||0|||||||||||||||
|
||||
HP|||RP:1||Обитаемая планета. Абиогенез.||0|||1|1|||||0.00025|0.0025||||5E+06|5E-05
|
||||
LP|||||На этой планете есть жизнь!||0|||||||||||||||
|
||||
EON1|||HP:100^RP:1||Архей. Не сразу бросается в глаза, но на таких планетах полно археев и цианобактерий.||1|||1|1|||||0.00025|0.0025||||1E+07|1E-06
|
||||
EON2|||EON1:1||Протерозой. Помимо вездесущих амёб, здесь присутствуют водоросли, губки и даже моллюски.||1|||1|1|||||0.00025|0.0025||||1E+07|1E-06
|
||||
EON3|||EON2:1000^EON1:1||Палеозой. Папоротники, черви, трилобиты и рыбы.||1|||1|1|||||0.00025|0.0025||||1E+06|1E-05
|
||||
EON4|||EON3:1000^EON1:1||Мезозой. Голосеменные, ящеры, динозавры и терапсиды.||1|||1|1|||||0.00025|0.0025||||1E+06|1E-05
|
||||
EON5|||EON4:2000^EON1:1||Кайнозой. Покрытосеменные и разнообразные млекопитающие, из них некоторые проявляют признаки интеллекта.||1|||1|1|||||0.00025|0.0025||||5E+05|5E-04
|
||||
C0|||||Докосмическая эпоха — от племени до федерации.||0|||||||||||||||
|
||||
C0.0|||EON5:1||Нижний палеолит. Приручение огня, каменные орудия.||1|0|0|1|1|||||0.0175|0.025||||2E+03|1
|
||||
C0.1|||C0.0:1||Средний палеолит. Развитые каменные орудия.||1|0|0|1|1|||||0.0175|0.025||||2E+03|1
|
||||
C0.2|||||Верхний палеолит. Сложные каменные орудия, искусство.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.2m|||C0.1:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.2b|||C0.1:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.2d|||C0.1:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.2l|||C0.1:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.2c|||C0.1:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.2w|||C0.1:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.3|||||Мезолит. Лук и стрелы, рыболовство, лодки.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.3m|||C0.2:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.3b|||C0.2:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.3d|||C0.2:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.3l|||C0.2:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.3c|||C0.2:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.3w|||C0.2:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.4|||||Ранний неолит. Земледелие.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.4m|||C0.3:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.4b|||C0.3:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.4d|||C0.3:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.4l|||C0.3:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.4c|||C0.3:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.4w|||C0.3:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.5|||||Средний неолит. Скотоводство, керамика.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.5m|||C0.4:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.5b|||C0.4:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.5d|||C0.4:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.5l|||C0.4:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.5c|||C0.4:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.5w|||C0.4:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.6|||||Поздний неолит. Крупные поселения.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.6m|||C0.5:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.6b|||C0.5:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.6d|||C0.5:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.6l|||C0.5:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.6c|||C0.5:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.6w|||C0.5:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.7|||||Первые царства медного и бронзового века. Колесо.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.7m|||C0.6:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.7b|||C0.6:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.7d|||C0.6:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.7l|||C0.6:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.7c|||C0.6:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.7w|||C0.6:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.8|||||Античные империи железного века. Философия.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.8m|||C0.7:1||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.8b|||C0.7:1||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.8d|||C0.7:1||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.8l|||C0.7:1||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.8c|||C0.7:1||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.8w|||C0.7:1||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C0.9|||||Промышленная эпоха. Электричество.||1|1|1|1|1||||||||||2E+03|1
|
||||
C0.9m|||C0.8:512^C0.7:256^EON5:21^EON1:6||Мистики|m|2||||||||1.6|0.0084|0.01|0.0000625|0.001024|||
|
||||
C0.9b|||C0.8:512^C0.7:256^EON5:43^EON1:9||Строители|b|2||||||||1|0.01859375|0.02|0.00003125|0.0025|||
|
||||
C0.9d|||C0.8:512^C0.7:256^EON5:7^EON1:3||Дипломаты|d|2||||||||1|0.0112|0.01538461538|0.0005|0.00015625|||
|
||||
C0.9l|||C0.8:512^C0.7:256^EON5:60^EON1:11||Логики|l|2||||||||0.5|0.0504|0.03571428571|0.0005|0.0025|||
|
||||
C0.9c|||C0.8:512^C0.7:256^EON5:252^EON1:19||Колонисты|c|2||||||||0.8|0.021|0.02604166667|0.004096|0.006103515625|||
|
||||
C0.9w|||C0.8:512^C0.7:256^EON5:260^EON1:26||Воины|w|2||||||||1.25|0.02625|0.03125|0.0005|0.016384|||
|
||||
C1|||||Цивилизации, осваивающие родную солнечную систему.||0|||||||||||||||
|
||||
C1.0|||||Цивилизация, освоившая родную планету. Сверхпроводимость, управляемый термоядерный синтез.||1|1|1|1|1||||||||||5E+03|1
|
||||
C1.0m|||C0.9:1024^EON5:21^EON1:6||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.0b|||C0.9:1024^EON5:43^EON1:9||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.0d|||C0.9:1024^EON5:7^EON1:3||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.0l|||C0.9:1024^EON5:60^EON1:11||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.0c|||C0.9:1024^EON5:152^EON1:19||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.0w|||C0.9:1024^EON5:260^EON1:26||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.1|||||Научные базы на других телах родной системы. Пусковая петля, космический лифт.||1|1|1|1|1||||||||||5E+03|1
|
||||
C1.1m|||C1.0:1024^C0.9:256^EON5:21^EON1:6||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.1b|||C1.0:1024^C0.9:256^EON5:43^EON1:9||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.1d|||C1.0:1024^C0.9:256^EON5:7^EON1:3||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.1l|||C1.0:1024^C0.9:256^EON5:60^EON1:11||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.1c|||C1.0:1024^C0.9:256^EON5:152^EON1:19||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.1w|||C1.0:1024^C0.9:256^EON5:260^EON1:26||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.2|||||Полное управление генетикой. Киборгизация.||1|1|1|1|1||||||||||5E+03|1
|
||||
C1.2m|||C1.1:256^C1.0:1024^C0.9:256^EON5:21^EON1:6||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.2b|||C1.1:256^C1.0:1024^C0.9:256^EON5:43^EON1:9||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.2d|||C1.1:256^C1.0:1024^C0.9:256^EON5:7^EON1:3||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.2l|||C1.1:256^C1.0:1024^C0.9:256^EON5:60^EON1:11||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.2c|||C1.1:256^C1.0:1024^C0.9:256^EON5:152^EON1:19||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.2w|||C1.1:256^C1.0:1024^C0.9:256^EON5:260^EON1:26||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.3|||||Промышленная добыча астероидов. Колонии на других планетах родной системы.||1|1|1|1|1||||||||||5E+03|1
|
||||
C1.3m|||C1.2:256^C1.0:1024^C0.9:64^EON5:13^EON1:13||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.3b|||C1.2:256^C1.0:1024^C0.9:64^EON5:22^EON1:22||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.3d|||C1.2:256^C1.0:1024^C0.9:64^EON5:5^EON1:5||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.3l|||C1.2:256^C1.0:1024^C0.9:64^EON5:30^EON1:30||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.3c|||C1.2:256^C1.0:1024^C0.9:64^EON5:64^EON1:64||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.3w|||C1.2:256^C1.0:1024^C0.9:64^EON5:100^EON1:100||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.4|||||Сбор вещества из атмосфер газовых гигантов.||1|1|1|1|1||||||||||5E+03|1
|
||||
C1.4m|||C1.3:384^C1.0:896^EON5:6^EON1:21||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.4b|||C1.3:384^C1.0:896^EON5:9^EON1:43||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.4d|||C1.3:384^C1.0:896^EON5:3^EON1:7||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.4l|||C1.3:384^C1.0:896^EON5:11^EON1:60||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.4c|||C1.3:384^C1.0:896^EON5:19^EON1:152||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.4w|||C1.3:384^C1.0:896^EON5:26^EON1:260||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.5|||||Космические города. Пикотехнологии: управление отдельными атомами.||1|1|1|1|1||||||||||5E+03|1
|
||||
C1.5m|||C1.4:512^C1.0:768^EON5:6^EON1:21||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.5b|||C1.4:512^C1.0:768^EON5:9^EON1:43||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.5d|||C1.4:512^C1.0:768^EON5:3^EON1:7||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.5l|||C1.4:512^C1.0:768^EON5:11^EON1:60||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.5c|||C1.4:512^C1.0:768^EON5:19^EON1:152||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.5w|||C1.4:512^C1.0:768^EON5:26^EON1:260||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.6|||||Промышленное производство антивещества. Первые звездолёты.||1|1|1|1|1|1|||||||||5E+03|1
|
||||
C1.6m|||C1.5:768^C1.0:512^EON5:6^RP:21||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.6b|||C1.5:768^C1.0:512^EON5:9^RP:43||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.6d|||C1.5:768^C1.0:512^EON5:3^RP:7||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.6l|||C1.5:768^C1.0:512^EON5:11^RP:60||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.6c|||C1.5:768^C1.0:512^EON5:19^RP:152||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.6w|||C1.5:768^C1.0:512^EON5:26^RP:260||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.7|||||Терраформинг. Гамма-лазеры.||1|1|1|1|1|1|||||||||5E+03|1
|
||||
C1.7m|||C1.6:896^C1.0:384^EON5:6^RP:21||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.7b|||C1.6:896^C1.0:384^EON5:9^RP:43||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.7d|||C1.6:896^C1.0:384^EON5:3^RP:7||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.7l|||C1.6:896^C1.0:384^EON5:11^RP:60||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.7c|||C1.6:896^C1.0:384^EON5:19^RP:152||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.7w|||C1.6:896^C1.0:384^EON5:26^RP:260||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.8|||||Космические станции континентального масштаба.||1|1|1|1|1|1|||||||||5E+03|1
|
||||
C1.8m|||C1.7:1024^C1.0:256^EON5:6^RP:21||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.8b|||C1.7:1024^C1.0:256^EON5:9^RP:43||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.8d|||C1.7:1024^C1.0:256^EON5:3^RP:7||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.8l|||C1.7:1024^C1.0:256^EON5:11^RP:60||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.8c|||C1.7:1024^C1.0:256^EON5:19^RP:152||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.8w|||C1.7:1024^C1.0:256^EON5:26^RP:260||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C1.9|||||Частичный рой Дайсона.||1|1|1|1|1|1|||||||||5E+03|1
|
||||
C1.9m|||C1.8:1024^C1.0:128^EON5:6^RP:21||Мистики|m|2||||||||1.6|0.009|0.01|0.0000625|0.001024|||
|
||||
C1.9b|||C1.8:1024^C1.0:128^EON5:9^RP:43||Строители|b|2||||||||1|0.019921875|0.02|0.00003125|0.0025|||
|
||||
C1.9d|||C1.8:1024^C1.0:128^EON5:3^RP:7||Дипломаты|d|2||||||||1|0.012|0.01538461538|0.0005|0.00015625|||
|
||||
C1.9l|||C1.8:1024^C1.0:128^EON5:11^RP:60||Логики|l|2||||||||0.5|0.054|0.03571428571|0.0005|0.0025|||
|
||||
C1.9c|||C1.8:1024^C1.0:128^EON5:19^RP:152||Колонисты|c|2||||||||0.8|0.0225|0.02604166667|0.004096|0.006103515625|||
|
||||
C1.9w|||C1.8:1024^C1.0:128^EON5:26^RP:260||Воины|w|2||||||||1.25|0.028125|0.03125|0.0005|0.016384|||
|
||||
C2|||||Цивилизации, ведущие экспансию к иным звёздам.||0|||||||||||||||
|
||||
C2.0|1E+00|DwStar||Звезда|Цивилизация, освоившая энергию родного солнца.||1|1|1|1|1|1|1|1|||||||5E+03|1
|
||||
C2.0m|||C1.9:1024^C1.0:128^RP:28||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.0b|||C1.9:1024^C1.0:128^EON5:9^RP:59||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.0d|||C1.9:1024^C1.0:128^EON5:3^RP:8||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.0l|||C1.9:1024^C1.0:128^EON5:11^RP:85||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.0c|||C1.9:1024^C1.0:128^EON5:19^RP:235||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.0w|||C1.9:1024^C1.0:128^EON5:26^RP:419||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.1|1E+01|DwStar|||Изменение планетарных орбит.||1|1|1|1|1|1|1|1|||||||5E+03|5.62E-01
|
||||
C2.1m|||C2.0:1||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.1b|||C2.0:1||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.1d|||C2.0:1||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.1l|||C2.0:1||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.1c|||C2.0:1||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.1w|||C2.0:1||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.2|1E+02|DwStar|||Управление инерцией.||1|1|1|1|1|1|1|1|||||||1E+04|3.16E-01
|
||||
C2.2m|||C2.1:1024^C2.0:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.2b|||C2.1:1024^C2.0:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.2d|||C2.1:1024^C2.0:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.2l|||C2.1:1024^C2.0:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.2c|||C2.1:1024^C2.0:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.2w|||C2.1:1024^C2.0:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.3|1E+03|DwStar||Рассеянное звёздное скопление|Сбор вещества со звёзд.||1|1|1|1|1|1|1|1|||||||2E+04|1.78E-01
|
||||
C2.3m|||C2.2:1024^C2.1:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.3b|||C2.2:1024^C2.1:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.3d|||C2.2:1024^C2.1:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.3l|||C2.2:1024^C2.1:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.3c|||C2.2:1024^C2.1:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.3w|||C2.2:1024^C2.1:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.4|1E+04|DwStar||Малое звёздное скопление|Фемтотехнологии: управление отдельными фермионами.||1|1|1|1|1|1|1|1|||||||2E+04|1.00E-01
|
||||
C2.4m|||C2.3:1024^C2.2:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.4b|||C2.3:1024^C2.2:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.4d|||C2.3:1024^C2.2:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.4l|||C2.3:1024^C2.2:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.4c|||C2.3:1024^C2.2:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.4w|||C2.3:1024^C2.2:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.5|1E+05|DwStar||Звёздное скопление|Деконструкция и сборка планет.||1|1|1|1|1|1|1|1|||||||3E+04|5.62E-02
|
||||
C2.5m|||C2.4:1024^C2.3:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.5b|||C2.4:1024^C2.3:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.5d|||C2.4:1024^C2.3:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.5l|||C2.4:1024^C2.3:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.5c|||C2.4:1024^C2.3:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.5w|||C2.4:1024^C2.3:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.6|1E+06|DwStar||Большое звёздное скопление|Астроархитектурная доминанта.||1|1|1|1|1|1|1|1|||||||3E+04|3.16E-02
|
||||
C2.6m|||C2.5:1024^C2.4:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.6b|||C2.5:1024^C2.4:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.6d|||C2.5:1024^C2.4:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.6l|||C2.5:1024^C2.4:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.6c|||C2.5:1024^C2.4:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.6w|||C2.5:1024^C2.4:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.7|1E+07|GiStar||Гигантское звёздное скопление|Освоение ресурсов звёзд-гигантов.||1|1|1|1|1|1|1|1|||||||4E+04|1.78E-02
|
||||
C2.7m|||C2.6:1024^C2.5:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.7b|||C2.6:1024^C2.5:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.7d|||C2.6:1024^C2.5:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.7l|||C2.6:1024^C2.5:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.7c|||C2.6:1024^C2.5:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.7w|||C2.6:1024^C2.5:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.8|1E+08|GiStar|||Управление гравитацией.||1|1|1|1|1|1|1|1|||||||4E+04|1.00E-02
|
||||
C2.8m|||C2.7:1024^C2.6:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.8b|||C2.7:1024^C2.6:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.8d|||C2.7:1024^C2.6:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.8l|||C2.7:1024^C2.6:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.8c|||C2.7:1024^C2.6:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.8w|||C2.7:1024^C2.6:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C2.9|1E+09|GiStar||Карликовая галактика|Перемещение звёзд.||1|1|1|1|1|1|1|1|||||||5E+04|5.62E-03
|
||||
C2.9m|||C2.8:1024^C2.7:6||Мистики|m|2||||||||1.6|0.006|0.01|0.0005|0.00128|1||
|
||||
C2.9b|||C2.8:1024^C2.7:9||Строители|b|2||||||||1|0.02125|0.015625|0.00003125|0.0025|0.64||
|
||||
C2.9d|||C2.8:1024^C2.7:3||Дипломаты|d|2||||||||1|0.0128|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C2.9l|||C2.8:1024^C2.7:11||Логики|l|2||||||||0.5|0.0576|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C2.9c|||C2.8:1024^C2.7:19||Колонисты|c|2||||||||0.8|0.024|0.04166666667|0.004096|0.006103515625|1||
|
||||
C2.9w|||C2.8:1024^C2.7:26||Воины|w|2||||||||1.25|0.03|0.025|0.000256|0.03072|0.25||
|
||||
C3|||||Цивилизации галактического масштаба.||0|||||||||||||||
|
||||
C3.0|1E+10|GiStar||Малая галактика|Экспансия в группу галактик.||1|1|1|1|1|1|1|1|||||||1E+05|3.16E-03
|
||||
C3.0m|||C2.9:1024^C2.8:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.0b|||C2.9:1024^C2.8:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.0d|||C2.9:1024^C2.8:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.0l|||C2.9:1024^C2.8:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.0c|||C2.9:1024^C2.8:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.0w|||C2.9:1024^C2.8:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.1|1E+11|GiStar||Средняя галактика|Создание звёзд.||1|1|1|1|1|1|1|1|||||||1E+05|1.78E-03
|
||||
C3.1m|||C3.0:1024^C2.9:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.1b|||C3.0:1024^C2.9:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.1d|||C3.0:1024^C2.9:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.1l|||C3.0:1024^C2.9:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.1c|||C3.0:1024^C2.9:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.1w|||C3.0:1024^C2.9:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.2|1E+12|WD||Большая галактика|Использование белых карликов.||1|1|1|1|1|1|1|1|||||||2E+05|1.00E-03
|
||||
C3.2m|||C3.1:1024^C3.0:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.2b|||C3.1:1024^C3.0:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.2d|||C3.1:1024^C3.0:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.2l|||C3.1:1024^C3.0:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.2c|||C3.1:1024^C3.0:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.2w|||C3.1:1024^C3.0:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.3|1E+13|WD||Гигантская галактика|Создание чёрных дыр из концентрированного излучения.||1|1|1|1|1|1|1|1|||||||3E+05|5.62E-04
|
||||
C3.3m|||C3.2:1024^C3.1:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.3b|||C3.2:1024^C3.1:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.3d|||C3.2:1024^C3.1:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.3l|||C3.2:1024^C3.1:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.3c|||C3.2:1024^C3.1:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.3w|||C3.2:1024^C3.1:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.4|1E+14|WD||Группа галактик|Экспансия в сверхскопление.||1|1|1|1|1|1|1|1|||||||4E+05|3.16E-04
|
||||
C3.4m|||C3.3:1024^C3.2:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.4b|||C3.3:1024^C3.2:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.4d|||C3.3:1024^C3.2:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.4l|||C3.3:1024^C3.2:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.4c|||C3.3:1024^C3.2:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.4w|||C3.3:1024^C3.2:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.5|1E+15|NS|||Использование нейтронных звёзд.||1|1|1|1|1|1|1|1|||||||5E+05|1.78E-04
|
||||
C3.5m|||C3.4:1024^C3.3:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.5b|||C3.4:1024^C3.3:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.5d|||C3.4:1024^C3.3:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.5l|||C3.4:1024^C3.3:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.5c|||C3.4:1024^C3.3:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.5w|||C3.4:1024^C3.3:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.6|1E+16|NS||Сверхскопление|Экспансия в нить.||1|1|1|1|1|1|1|1|||||||6E+05|1.00E-04
|
||||
C3.6m|||C3.5:1024^C3.4:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.6b|||C3.5:1024^C3.4:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.6d|||C3.5:1024^C3.4:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.6l|||C3.5:1024^C3.4:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.6c|||C3.5:1024^C3.4:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.6w|||C3.5:1024^C3.4:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.7|1E+17|MGT|||Использование магнетаров.||1|1|1|1|1|1|1|1|||||||7E+05|5.62E-05
|
||||
C3.7m|||C3.6:1024^C3.5:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.7b|||C3.6:1024^C3.5:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.7d|||C3.6:1024^C3.5:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.7l|||C3.6:1024^C3.5:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.7c|||C3.6:1024^C3.5:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.7w|||C3.6:1024^C3.5:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.8|1E+18|MGT||Галактическая нить|Экспансия в великую стену.||1|1|1|1|1|1|1|1|||||||8E+05|3.16E-05
|
||||
C3.8m|||C3.7:1024^C3.6:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.8b|||C3.7:1024^C3.6:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.8d|||C3.7:1024^C3.6:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.8l|||C3.7:1024^C3.6:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.8c|||C3.7:1024^C3.6:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.8w|||C3.7:1024^C3.6:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C3.9|1E+19|BH|||Использование чёрных дыр.||1|1|1|1|1|1|1|1|||||||9E+05|1.78E-05
|
||||
C3.9m|||C3.8:1024^C3.7:6||Мистики|m|2||||||||1.6|0.006375|0.01|0.0005|0.00128|1||
|
||||
C3.9b|||C3.8:1024^C3.7:9||Строители|b|2||||||||1|0.022578125|0.015625|0.00003125|0.0025|0.64||
|
||||
C3.9d|||C3.8:1024^C3.7:3||Дипломаты|d|2||||||||1|0.0136|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C3.9l|||C3.8:1024^C3.7:11||Логики|l|2||||||||0.5|0.0612|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C3.9c|||C3.8:1024^C3.7:19||Колонисты|c|2||||||||0.8|0.0255|0.04166666667|0.004096|0.006103515625|1||
|
||||
C3.9w|||C3.8:1024^C3.7:26||Воины|w|2||||||||1.25|0.031875|0.025|0.000256|0.03072|0.25||
|
||||
C4|||||Цивилизации вселенского масштаба.||0|||||||||||||||
|
||||
C4.0|1E+20|BH|||Манипуляции со временем.||1|1|1|1|1|1|1|1|||||||2E+06|1.00E-05
|
||||
C4.0m|||C3.9:1024^C3.8:6||Мистики|m|2||||||||1.6|0.00675|0.01|0.0005|0.00128|1||
|
||||
C4.0b|||C3.9:1024^C3.8:9||Строители|b|2||||||||1|0.02390625|0.015625|0.00003125|0.0025|0.64||
|
||||
C4.0d|||C3.9:1024^C3.8:3||Дипломаты|d|2||||||||1|0.0144|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C4.0l|||C3.9:1024^C3.8:11||Логики|l|2||||||||0.5|0.0648|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C4.0c|||C3.9:1024^C3.8:19||Колонисты|c|2||||||||0.8|0.027|0.04166666667|0.004096|0.006103515625|1||
|
||||
C4.0w|||C3.9:1024^C3.8:26||Воины|w|2||||||||1.25|0.03375|0.025|0.000256|0.03072|0.25||
|
||||
C4.1|1E+21|BH||Великая стена|Экспансия на масштабы всей вселенной.||1|1|1|1|1|1|1|1|||||||2E+06|5.62E-06
|
||||
C4.1m|||C4.0:1024^C3.9:6||Мистики|m|2||||||||1.6|0.00675|0.01|0.0005|0.00128|1||
|
||||
C4.1b|||C4.0:1024^C3.9:9||Строители|b|2||||||||1|0.02390625|0.015625|0.00003125|0.0025|0.64||
|
||||
C4.1d|||C4.0:1024^C3.9:3||Дипломаты|d|2||||||||1|0.0144|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C4.1l|||C4.0:1024^C3.9:11||Логики|l|2||||||||0.5|0.0648|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C4.1c|||C4.0:1024^C3.9:19||Колонисты|c|2||||||||0.8|0.027|0.04166666667|0.004096|0.006103515625|1||
|
||||
C4.1w|||C4.0:1024^C3.9:26||Воины|w|2||||||||1.25|0.03375|0.025|0.000256|0.03072|0.25||
|
||||
C4.2|1E+22|SMBH|||Использование сверхмассивных чёрных дыр.||1|1|1|1|1|1|1|1|||||||4E+06|3.16E-06
|
||||
C4.2m|||C4.1:1024^C4.0:6||Мистики|m|2||||||||1.6|0.00675|0.01|0.0005|0.00128|1||
|
||||
C4.2b|||C4.1:1024^C4.0:9||Строители|b|2||||||||1|0.02390625|0.015625|0.00003125|0.0025|0.64||
|
||||
C4.2d|||C4.1:1024^C4.0:3||Дипломаты|d|2||||||||1|0.0144|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C4.2l|||C4.1:1024^C4.0:11||Логики|l|2||||||||0.5|0.0648|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C4.2c|||C4.1:1024^C4.0:19||Колонисты|c|2||||||||0.8|0.027|0.04166666667|0.004096|0.006103515625|1||
|
||||
C4.2w|||C4.1:1024^C4.0:26||Воины|w|2||||||||1.25|0.03375|0.025|0.000256|0.03072|0.25||
|
||||
C4.3|1E+23|SMBH|||Складное пространство.||1|1|1|1|1|1|1|1|||||||6E+06|1.78E-06
|
||||
C4.3m|||C4.2:1024^C4.1:6||Мистики|m|2||||||||1.6|0.00675|0.01|0.0005|0.00128|1||
|
||||
C4.3b|||C4.2:1024^C4.1:9||Строители|b|2||||||||1|0.02390625|0.015625|0.00003125|0.0025|0.64||
|
||||
C4.3d|||C4.2:1024^C4.1:3||Дипломаты|d|2||||||||1|0.0144|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C4.3l|||C4.2:1024^C4.1:11||Логики|l|2||||||||0.5|0.0648|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C4.3c|||C4.2:1024^C4.1:19||Колонисты|c|2||||||||0.8|0.027|0.04166666667|0.004096|0.006103515625|1||
|
||||
C4.3w|||C4.2:1024^C4.1:26||Воины|w|2||||||||1.25|0.03375|0.025|0.000256|0.03072|0.25||
|
||||
C4.4|1E+24|SMBH||Вселенский пузырь|Поле невероятности.||1|1|1|1|1|1|1|1|||||||8E+06|1.00E-06
|
||||
C4.4m|||C4.3:1024^C4.2:6||Мистики|m|2||||||||1.6|0.00675|0.01|0.0005|0.00128|1||
|
||||
C4.4b|||C4.3:1024^C4.2:9||Строители|b|2||||||||1|0.02390625|0.015625|0.00003125|0.0025|0.64||
|
||||
C4.4d|||C4.3:1024^C4.2:3||Дипломаты|d|2||||||||1|0.0144|0.01923076923|0.0009765625|0.0003125|2.56||
|
||||
C4.4l|||C4.3:1024^C4.2:11||Логики|l|2||||||||0.5|0.0648|0.04464285714|0.0005|0.0025|1.5625||
|
||||
C4.4c|||C4.3:1024^C4.2:19||Колонисты|c|2||||||||0.8|0.027|0.04166666667|0.004096|0.006103515625|1||
|
||||
C4.4w|||C4.3:1024^C4.2:26||Воины|w|2||||||||1.25|0.03375|0.025|0.000256|0.03072|0.25||
|
||||
Ct|||||Трансцендентальность.||0|1||||||||||||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
ID | Name | Age | Email
|
||||
1 | John | 30 | john@example.com
|
||||
2 | Jane | 25 | jane@example.com
|
||||
|
@@ -1,130 +0,0 @@
|
||||
Owniverse||interface||||
|
||||
MM-Create||interface||||Menu/en/CreateUniverse
|
||||
MM-Help||interface||||Menu/en/Tutorial
|
||||
MM-Achievments||interface||||Menu/en/Achievements
|
||||
MM-Credits||interface||||Menu/en/Credits
|
||||
MM-LanguageEn||interface||||Menu/en/EnglishActive
|
||||
MM-LanguageRu||interface||||Menu/en/Russian
|
||||
MM-SoundActive||interface||||Menu/en/SoundActive
|
||||
MM-SoundNonActive||interface||||Menu/en/SoundNonActive
|
||||
MM-MusicActive||interface||||Menu/en/MusicActive
|
||||
MM-MusicNonActive||interface||||Menu/en/MusicNonActive
|
||||
MM-LeaveReality||interface||||Menu/en/LeaveReality
|
||||
CreditsTitle||interface||||Credits/en/Credits
|
||||
CreditsRoles||interface||Unitizer <br> Python Tamer <br> Painter <br> Original Score by||
|
||||
CreditsNames||interface||Kirill Ivlev <br> Ilya Konovalov <br> Dmitry Uvarov <br> Tatyana Stepanenko||
|
||||
Achievement-Header||interface||||Achievements/en/Achievements
|
||||
Achievement-Status-Template||interface||Unlocked %Count% of %Total%||
|
||||
ReportTitle||interface||||Report/en/Title
|
||||
ReportNewWarning||interface||||Report/en/NewWarning
|
||||
ReportNewButton||interface||||Report/en/NewButton
|
||||
ReportWipeWarning||interface||||Report/en/WipeWarning
|
||||
ReportWipeButton||interface||||Report/en/WipeButton
|
||||
MF-FBV-ReturnButton||interface||||Dashboard/Indicators/ForceBoxes/En/Return
|
||||
MF-FBV-OpenBoxButton||interface||||Dashboard/Indicators/ForceBoxes/En/OpenBox
|
||||
MF-FBV-AcquireButton||interface||||Dashboard/Indicators/ForceBoxes/En/Acquire
|
||||
MF-FBV-NoAdsDeliveredSign||interface||||Dashboard/Indicators/ForceBoxes/En/NoAds
|
||||
Universe||interface||||
|
||||
Time|Time|indicator|AboutTheGame|Time|Change the speed as you like.|
|
||||
Energy|Enr|indicator|EnergyAndDensity|Energy|Spend on hydrogen and space.|
|
||||
Density|Den|indicator|EnergyAndDensity|Density|Maintain balance.|
|
||||
DensityRip|DenR|indicator|EnergyAndDensity|Big Rip|Increase density!|
|
||||
DensityCrunch|DenC|indicator|EnergyAndDensity|Big Crunch|Decrease density!|
|
||||
BurnabilityIndicator|Bur|indicator|BurnabilityAndExplosiveness|Burnability|Burning intensity of stars.|
|
||||
ExplosivenessIndicator|Exp|indicator|BurnabilityAndExplosiveness|Explosiveness|Power of stellar explosions.|
|
||||
AggressionIndicator|Agg|indicator|AggressionAndTrends|Aggression|Evolution and competition.|
|
||||
||||1 year per second||
|
||||
||||10 years per second||
|
||||
||||100 years per second||
|
||||
||||1 000 years per second||
|
||||
||||10 000 years per second||
|
||||
||||100 000 years per second||
|
||||
||||1 000 000 years per second||
|
||||
||||10 000 000 years per second||
|
||||
||||100 000 000 years per second||
|
||||
||||1 000 000 000 years per second||
|
||||
||logtext||Summary of changes in the last 100 seconds.<br>Average values for the year are shown.||
|
||||
ForceNavigationImage||interface||||BigBang/Dashboard/en/Forces
|
||||
1-1-1-NavigationResourceImage||interface||||BigBang/Dashboard/en/Resources
|
||||
Space|S|entity|SpaceAndHydrogen|Space|Needed for placing objects.|
|
||||
Hydrogen|H|entity|SpaceAndHydrogen|Hydrogen|Used to create clouds and stars.|
|
||||
Helium|He|entity|HeliumAndMetals|Helium|Saves hydrogen.|
|
||||
Metals|Met|entity|HeliumAndMetals|Metals|Needed to create planets.|
|
||||
||||Metallicity||
|
||||
NavigationStarImage||interface||||BigBang/Dashboard/en/Stars
|
||||
Nebula|N|entity|Clouds|Nebula|Produces hydrogen.|
|
||||
Globula|G|entity|Clouds|Globula|Produces more hydrogen.|
|
||||
StarNursery|STN|entity|Clouds|Star Nursery|Produces hydrogen and stars.|
|
||||
MolecularCloud|MC|entity|Clouds|Molecular Cloud|Produces a lot of hydrogen.|
|
||||
ActiveStarNursery|ASTN|entity|Clouds|Active Star Nursery|Produces a lot of hydrogen and stars.|
|
||||
BrownDwarf|BD|entity|DwarfStars|Brown Dwarf|Useless, but doesn't take up space.|
|
||||
RedDwarf|RD|entity|DwarfStars|Red Dwarf|The simplest star.|
|
||||
OrangeDwarf|OD|entity|DwarfStars|Orange Dwarf|Star with planets.|
|
||||
YellowDwarf|YD|entity|DwarfStars|Yellow Dwarf|Star similar to the Sun.|
|
||||
WhiteYellowDwarf|WYD|entity|DwarfStars|White-Yellow Dwarf|Star with many planets.|
|
||||
RedGiant|RG|entity|GiantStars|Red Giant|Degrading old star.|
|
||||
YellowGiant|YG|entity|GiantStars|Yellow Giant|Optimal for metals production.|
|
||||
BlueGiant|BG|entity|GiantStars|Blue Giant|Produces metals and space in a balanced manner.|
|
||||
Supergiant|SG|entity|GiantStars|Supergiant|Produces space and a bit of metals quickly.|
|
||||
Hypergiant|HG|entity|GiantStars|Hypergiant|Burns violently, creating a lot of space.|
|
||||
WhiteDwarf|WD|entity|DegenerateObjects|White Dwarf|Remnant of a dwarf star.|
|
||||
NeutronStar|NS|entity|DegenerateObjects|Neutron Star|Remnant of a burned-out giant.|
|
||||
Magnetar|MGT|entity|DegenerateObjects|Magnetar|Remnant of a large giant.|
|
||||
BlackHole|BH|entity|DegenerateObjects|Black Hole|The result of a stellar catastrophe. Needed for structures.|
|
||||
SupermassiveBlackHole|SMBH|entity|DegenerateObjects|Supermassive Black Hole|Merged from black holes. Needed for galaxies.|
|
||||
1-3-1-NavigationStructureImage||interface||||BigBang/Dashboard/en/Structures
|
||||
StarClusterScattered|SCD|entity|StarClusters|Scattered Star Cluster|A little structure of irregular shape.<br>Space: +10%.|
|
||||
StarClusterSmall|SCS|entity|StarClusters|Small Star Cluster|Has globular shape.<br>Space: +20%.|
|
||||
StarClusterMedium|SCM|entity|StarClusters|Medium Star Cluster|Noticeable from afar off.<br>Space: +30%.|
|
||||
StarClusterLarge|SCL|entity|StarClusters|Large Star Cluster|Makes an impression.<br>Space: +50%.|
|
||||
StarClusterGiant|SCG|entity|StarClusters|Giant Star Cluster|Causes awe.<br>Space: +80%.|
|
||||
GalaxyEllipticalDwarf|GED|entity|Galaxies|Dwarf Elliptical Galaxy|The size of the Small Magellanic Cloud.<br>Space: +100%.|
|
||||
GalaxyEllipticalSmall|GES|entity|Galaxies|Small Elliptical Galaxy|The size of the Large Magellanic Cloud.<br>Space: +150%.|
|
||||
GalaxyEllipticalMedium|GEM|entity|Galaxies|Medium Elliptical Galaxy|The size of the Milky Way.<br>Space: +200%.|
|
||||
GalaxyEllipticalLarge|GEL|entity|Galaxies|Large Elliptical Galaxy|The size of the Andromeda Galaxy.<br>Space: +300%.|
|
||||
GalaxyEllipticalGiant|GEG|entity|Galaxies|Giant Elliptical Galaxy|Similar to the IC 1101.<br>Space: +450%.|
|
||||
GalaxySpiralDwarf|GSD|entity|Galaxies|Dwarf Spiral Galaxy|Similar to the Small Magellanic Cloud.|
|
||||
GalaxySpiralSmall|GSS|entity|Galaxies|Small Spiral Galaxy|Similar to the Large Magellanic Cloud.|
|
||||
GalaxySpiralMedium|GSM|entity|Galaxies|Medium Spiral Galaxy|Similar to the Milky Way.|
|
||||
GalaxySpiralLarge|GSL|entity|Galaxies|Large Spiral Galaxy|Similar to the Andromeda Galaxy.|
|
||||
GalaxySpiralGiant|GSG|entity|Galaxies|Giant Spiral Galaxy|The size of the IC 1101.|
|
||||
GalacticGroup|LGG|entity|LargeStructures|Galactic Group|Aggregates galaxies.|
|
||||
GalacticSupercluster|LSC|entity|LargeStructures|Galactic Supercluster|Basis for the structure of the universe.|
|
||||
GalacticFilament|LGF|entity|LargeStructures|Galactic Filament|Balances density with voids.|
|
||||
GalacticWall|LGW|entity|LargeStructures|Great Wall|Creates the structure of the universe.|
|
||||
UniverseBubble|LUB|entity|LargeStructures|Universe Bubble|That's the extent of it.|
|
||||
1-4-1-NavigationLifeImage||interface||||BigBang/Dashboard/en/Planets
|
||||
GasGiant|GG|entity|Planets|Gas Giant|Too big.|
|
||||
AsteroidBelt|AB|entity|Planets|Asteroids|A bunch of rocks.|
|
||||
RockyPlanet|RP|entity|Planets|Rocky Planet|Not suitable for life.|
|
||||
HabitablePlanet|HP|entity|Planets|Habitable Planet|Has the potential for life.|
|
||||
LivingPlanet|LP|entity|Planets|Living Planet|There is life on this planet!|
|
||||
|Star|||Star||
|
||||
|SC|||Star Cluster||
|
||||
|GAL|||Galaxy||
|
||||
AboutTheGame||help||About The Game|In this game you can <b>create a universe</b> and control its development. The final goal is designated by yourself. Don't forget to check out the list of game achievements and try to reach some of the ones that seem interesting. Read hints, explore possibilities and don't be afraid to experiment. In the worst case you can always start from scratch.<br><br>Numbers greater than 1 000 are displayed using <b>scientific notation</b>. 1e4 – this is a 10 000, meaning “one with 4 zeroes”. 1e6 – a million, 1e9 – a billion and so on. 13.77e9 – this is 13 billions 770 millions.<br><br>The game plays in <b>real time</b> with changeable scaling. One second of real time can pass a year, a decade, a century and so on. Change the pace of time by using buttons “−” and “+”.|Help/en/AboutTheGame
|
||||
GameplayBasics||help||Gameplay Basics|You have access to a very large, but still limited <b>cache of energy</b> that is used to create the whole matter in the universe and the expanse of the universe's space itself. At the start of the game the vast majority of this energy is not in your direct control. But during the game you will take more and more energy from the cache. The less filled the cache is, the harder it is to obtain new energy. The energy shortage won’t become an urgent problem in the first billion years or so: the cache is really very large.<br><br>The matter is harvested from the energy cache in the form of <b>hydrogen</b> by clouds. Hydrogen may be spent to create new clouds and also to create stars. During their burning stars create <b>space</b> that can be used to place additional stars and clouds. This creation of space also spends some energy from the cache.|Help/en/GameplayBasics
|
||||
StarsAndStructures||help||Stars and Structures|The heavier the star is, the faster it burns and the more space it produces. However, on the contrary, the most cost-efficient way of spending hydrogen to obtain new space, is investment into the smallest stars. Unfortunately they burn for a very long time and produce space very slowly. Also each star takes a unit of space, no matter which type the star is.<br><br>There is a reason to arrange stars and nebulae of the growing universe into galaxies and even larger <b>structures</b>. The production of space by stars is increased inside structures.<br><br>The proportion between universe matter and space is called density. The universe is only stable when the <b>density</b> is inside a certain range. If it is too packed or too empty, problems come into being.|Help/en/StarsAndStructures
|
||||
LifeAndForces||help||Life and Forces|<b>Planets</b> form around certain stars, and life may emerge there. In specific circumstances life can evolve to sapience, and sapience can advance to high technology. Life's next step is to try to reclaim the universe, which she assumes as her own.<br><br>There are many events in the universe that don't happen all the time, only with certain probability. You have access to special <b>forces</b>, which you can use to “tweak” chances of events that are important to you, changing the laws of nature in the process.|Help/en/LifeAndForces
|
||||
Achievements||help||Achievements|During the gameplay you will definitely be able to reach certain <b>achievements</b>. While simpler ones come by themselves, ones that are more complex may be more tricky. You can use the achievements list as a roadmap of hints and suggestions for possible next steps.<br><br>It is unnecessary to try to finish each and every achievement. This is your own universe and you can do whatever you like with it. However, achievements are a nice thing in general, and besides just having a personal feeling of pride and accomplishment you can easily <b>share</b> it with friends!|Help/en/Achievements
|
||||
Karma||help||Karma|Each obtained achievement grants you a certain number of <b>karmic points</b>. During the new universe creation you may spend these points to gain some small bonuses that will definitely help you to gain better results.<br><br>Spent karmic points are not lost: they will be <b>available</b> for spending again in the next universe. There is a chance that there will be even more of them at that moment.|Help/en/Karma
|
||||
Gauges||help||Gauges|<b>Gauges</b> of measures that are especially important for an active objects page are located on the dashboard. To check other gauges just swipe the page.|Help/en/Gauges
|
||||
EnergyAndDensity||help||Energy and Density|<b>Energy</b> from the cache is converted into hydrogen and space. The lower the proportion of remaining cache, indicated in the middle of the bar, the lower is the efficiency of the resource generators, such as clouds for hydrogen and stars for space.<br>The energy cache really is immense, however it is not infinite.<br><br>Average <b>density</b> of the universe is the ratio of total matter mass, meaning all stars, clouds and so on, to the empty space. It increases alongside the mass and decreases when the space grows. If the “ρ” gauge will leave the “middle zone” and become inclined to any of the two extremeties, then the outcome might surprise you in a bad way.|Help/en/EnergyAndDensity
|
||||
BurnabilityAndExplosiveness||help||Burnability and Explosiveness|<b>Burnability</b> displays the current burning rate of stars. It can be influenced by the universe density (the denser the greater), the actions of superadvanced civilizations and the forces used by the player.<br><br><b>Explosiveness</b> displays the current probability of the gamma-ray bursts and the space yield produced by any sort of star collapses. It can be influenced by universe density (the denser the lower), actions of superadvanced civilizations and forces used by the player.|Help/en/BurnabilityAndExplosiveness
|
||||
AggressionAndTrends||help||Aggression and Trends|<b>Aggression</b> displays the intensity of competition between species, civilizations and their inner factions. It governs the progress and stability of life and societies. It can be influenced by universal trends and forces used by the player.<br>By default the average universal aggression is shown. But you can select any civilization level and observe the average aggression for this level, which can be different from the universal one.<br><br><b>Trends</b> are the results of prolonged civilization influences. There are five trends in total.|Help/en/AggressionAndTrends
|
||||
SpaceAndHydrogen||help||Space and Hydrogen|The majority of creative actions require spending <b>resources</b>.<br><br><b>Space</b> is needed to place everything you are creating. To add new space lit up some stars! Also don't forget to combine the created stars into structures to get even more space.<br><br><b>Hydrogen</b> is required to create nebulae and stars. Nebulae allow you to obtain more hydrogen. Stars are used to make more space and also to harbor planets. Both of these object types also serve as building blocks to create more complex structures.|Help/en/SpaceAndHydrogen
|
||||
HeliumAndMetals||help||Helium and Metals|Helium and metals are obtained when stars finish their life cycle.<br><b>Helium</b> is not especially useful, but it is inevitable. It is automatically spent in a line of hydrogen if possible, conserving the more valuable resource.<br><br><b>Metals</b> “contaminate” your hydrogen and helium by heavier elements. And usually it is a good thing, because the high proportion of metals to hydrogen and helium, also known as “metallicity”, provides for a greater chance for planets to emerge near new stars.<br>Metals are added when stars burn out, especially larger ones. But total metellicity may shrink down when the universe is filled by new objects made from pure hydrogen and helium, such as nebulae and stars.|Help/en/HeliumAndMetals
|
||||
Clouds||help||Clouds|Clouds are used to produce hydrogen, which is required to construct the universe. Simplest nebulae can be arranged into more complex cloud structures.<br><br><b>Nebula:</b> the simplest of clouds.<br><b>Globula:</b> provides hydrogen at a slower rate than nebulae, but persists for longer, and also takes less space. Used in creation of larger clouds.<br><b>Star nursery:</b> automatically creates stars from a fraction of provided hydrogen.<br><b>Molecular cloud:</b> large and long-lasting hydrogen generator.<br><b>Active star nursery:</b> very large automatic hydrogen and stars generator.|Help/en/Clouds
|
||||
DwarfStars||help||Dwarf Stars|Dwarf stars are cheap, they last for a long time and produce space efficiently. They can have planets that sometimes may harbor life at some point.<br><br><b>Brown dwarf:</b> useless rejects that happen during the star creation process. They appear by themselves and do not take space as opposed to all the other stars. Can’t be created manually, and you don’t need to.<br><b>Red dwarf:</b> It is cost-effective in space creation, but works very slowly.<br><b>Orange dwarf:</b> perfect for the development of life.<br><b>Yellow dwarf:</b> good for the emergence of life.<br><b>White-yellow dwarf:</b> has a largest habitable zone, but burns out rapidly.|Help/en/DwarfStars
|
||||
GiantStars||help||Giant Stars|Giant stars burn rapidly and produce a lot of space. At the end of their existence they collapse. These events can damage planets at nearby stars, but they produce metals.<br><br><b>Red giant:</b> a star that has consumed the majority of its fuel. You can’t create these manually, they emerge themselves.<br><b>Yellow giant:</b> the most modest of giant stars. Produces a lot of metals, collapse is weak.<br><b>Blue giant:</b> a common giant star. Produces a good amount of space and metals. Collapse is average.<br><b>Supergiant:</b> huge giant star. Lives short, collapses violently, produces a lot of space and few metals.<br><b>Hypergiant:</b> extremely giant star. Always collapses into a black hole, producing a huge amount of space, which is accompanied by insane destruction.|Help/en/GiantStars
|
||||
DegenerateObjects||help||Degenerate Objects|Stars that have exhausted their fusion fuel. They occupy space and do not produce anything, but can be used in forming structures.<br><br><b>White dwarf:</b> the remains of a dwarf star.<br><b>Neutron star:</b> exceptionally compact remains of a giant star.<br><b>Magnetar:</b> the most dense object that is not a black hole.<br><b>Black hole:</b> the remains of a giant star that experienced a destructive collapse. They may be merged into supermassive black holes.<br><b>Supermassive black hole:</b> the result of multitudes of the stellar mass black holes merged into one. Required for galactic formation. They do grow in time, consuming other stars, but do so very slowly.|Help/en/DegenerateObjects
|
||||
StarClusters||help||Star Clusters|Star clusters are the most modest of available structures. As a matter of fact these are just a lot of stars which are co-located closer to each other than usual, and therefore they are gravitationally bound. But even such simple lumps already bring noticeable variety into the homogenous landscape of the universe.<br><br><b>Scattered:</b> little structure of irregular shape.<br><b>Small:</b> globular shape.<br><b>Medium:</b> noticeable from afar off.<br><b>Large:</b> makes an impression.<br><b>Giant:</b> causes awe.<br><br>Clusters, like other structures, increase the production of space for their constituent stars. Stars in structures exist as usual. Over time, the clusters disintegrate, releasing the stars they are composed of. Large clusters last longer than small ones and provide a larger bonus to space production.|Help/en/StarClusters
|
||||
Galaxies||help||Galaxies|<b>Elliptical galaxies</b> resemble colossal star clusters. They have lenticular shapes, rotate slowly and look very much alike to each other. The stars in them produce more space than in spiral galaxies. Gradually, elliptical galaxies disintegrate into separate stars.<br><br><b>Spiral galaxies</b> spin faster than elliptical ones, they have diverse structures with arms and keep active star formation. They are better fit for the emergence and development of life. In time spiral galaxies exhaust their storage of free gas, they slow down and become more stable and boring ellipticals.<br><br>As in the case of other structures, the stars included in the composition of galaxies continue to function as usual. They produce space, and dwarf stars can have planets.|Help/en/Galaxies
|
||||
LargeStructures||help||Large Structures|<b>Large structures</b> are called that for a reason: whole galaxies are used there as building blocks. They are needed to arrange the universe on the largest scales. Without them the space, uniformly filled by stars and galaxies, becomes too monotonous and sparse, meanwile these structures create a packing gradient with more and less dense regions. This arrangement is better fit for being claimed and developed by superadvanced sapient beings.<br><br>Over time, large structures disintegrate into their components, but this does not happen very soon.|Help/en/LargeStructures
|
||||
Planets||help||Planets|Planets emerge around dwarf stars when metals are sufficient. In a sense all this space construction is being made with a purpose of making planets with potential of bearing life.<br><br><b>Gas giant:</b> large planet with a very thick atmosphere, unsuitable for life.<br><b>Asteroid belt:</b> a bunch of rocks that lacks mass to form a planet.<br><b>Rocky planet:</b> a planet unsuitable for life. Too cold, hot or wrong chemical makeup.<br><b>Habitable planet:</b> there is liquid water here and life may emerge in the long run.<br><b>Living planet:</b> denizens of this planet show great promise!|Help/en/Planets
|
||||
Eons||help||Eons|Life slowly evolves through eons from the most primitive one to the one with sapience potential. Not all planets are successful in completing this process to the end, because stars do not burn forever.<br><br><b>Archean:</b> it is difficult to notice, but these planets are full of archaea and cyanobacteria.<br><b>Paleozoic:</b> alongside ubiquitous amoeba these planets host algae, sponges and even molluscs.<br><b>Proterozoic:</b> ferns, worms, trilobites and fishes.<br><b>Mezozoic:</b> conifers, lizards, dinosaurs and therapsids.<br><b>Cenozoic:</b> flowering plants and various mammals, some of which show signs of sapience.|Help/en/Eons
|
||||
Type0Civilizations||help||Type 0 Civilizations|Pre-space epoch – from a tribe to a federation.<br><br><b>c0.0:</b> Lower Paleolithic. Control of fire, stone tools.<br><b>c0.1:</b> Middle Paleolithic. Developed stone tools.<br><b>c0.2:</b> Upper Paleolithic. Advanced stone tools, art.<br><b>c0.3:</b> Mesolithic. Bow and arrows, fishing, boats.<br><b>c0.4:</b> Early Neolithic. Agriculture.<br><b>c0.5:</b> Middle Neolithic. Animal husbandry, ceramics.<br><b>c0.6:</b> Late Neolithic. Large settlements.<br><b>c0.7:</b> First kingdoms of copper and bronze ages. Wheel.<br><b>c0.8:</b> Ancient empires of the iron age. Philosophy.<br><b>c0.9:</b> Industrial age. Electronics and nuclear energy.|Help/en/Type0Civilizations
|
||||
Type1Civilizations||help||Type 1 Civilizations|Civilizations are busy with settling their own solar system.<br><br><b>c1.0:</b> A civilization that has completely mastered its home planet. Superconductivity, controlled nuclear fusion.<br><b>c1.1:</b> Research compounds in the home system. Launch loop, space elevator.<br><b>c1.2:</b> Complete genetic tailoring. Cyborgization.<br><b>c1.3:</b> Industrial asteroid mining. Colonies on the other planets of the home system.<br><b>c1.4:</b> Harvesting of matter from atmospheres of gas giants.<br><b>c1.5:</b> Space cities. Picotechnologies.<br><b>c1.6:</b> Industrial manufacturing of antimatter. First starships.<br><b>c1.7:</b> Terraforming. Gamma-lasers.<br><b>c1.8:</b> Space stations of continental scale.<br><b>c1.9:</b> Partial Dyson swarm.|Help/en/Type1Civilizations
|
||||
Type2Civilizations||help||Type 2 Civilizations|Civilizations that can expand to other stars.<br><br><b>c2.0:</b> A civilization that completely exploits the energy output of its own sun.<br><b>c2.1:</b> Change of planetary orbits.<br><b>c2.2:</b> Control over inertia.<br><b>c2.3:</b> Starlifting, harvesting matter from stars.<br><b>c2.4:</b> Femtotechnologies: manipulations on individual fermions.<br><b>c2.5:</b> Deconstruction and creation of planets.<br><b>c2.6:</b> Complete domination of astroarchitecture over planets.<br><b>c2.7:</b> Giant stars resources exploitation.<br><b>c2.8:</b> Control over gravity.<br><b>c2.9:</b> Movement of stars.|Help/en/Type2Civilizations
|
||||
Type3Civilizations||help||Type 3 Civilizations|Civilizations of galactic scale.<br><br><b>c3.0:</b> Expansion into a galactic group is available.<br><b>c3.1:</b> Creation of stars.<br><b>c3.2:</b> Utilization of white dwarfs.<br><b>c3.3:</b> Creation of black holes by focused radiation.<br><b>c3.4:</b> Expansion into a galactic supercluster is available.<br><b>c3.5:</b> Utilization of neutron stars.<br><b>c3.6:</b> Expansion into a galactic filament is available.<br><b>c3.7:</b> Utilization of magnetars.<br><b>c3.8:</b> Expansion into a great wall is available.<br><b>c3.9:</b> Utilization of black holes.|Help/en/Type3Civilizations
|
||||
Type4Civilizations||help||Type 4 Civilizations|Civilizations of the universe scale.<br><br><b>c4.0:</b> Temporal manipulations.<br><b>c4.1:</b> Expansion into the whole universe scale is available.<br><b>c4.2:</b> Utilization of supermassive black holes.<br><b>c4.3:</b> Folding space.<br><b>c4.4:</b> Improbability field.<br><br>And so on, bigger and bigger, so long as there is space.|Help/en/Type4Civilizations
|
||||
|
@@ -1,130 +0,0 @@
|
||||
Owniverse||interface||||
|
||||
MM-Create||interface||||Menu/ru/CreateUniverse
|
||||
MM-Help||interface||||Menu/ru/Tutorial
|
||||
MM-Achievments||interface||||Menu/ru/Achievements
|
||||
MM-Credits||interface||||Menu/ru/Credits
|
||||
MM-LanguageEn||interface||||Menu/ru/English
|
||||
MM-LanguageRu||interface||||Menu/ru/RussianActive
|
||||
MM-SoundActive||interface||||Menu/ru/SoundActive
|
||||
MM-SoundNonActive||interface||||Menu/ru/SoundNonActive
|
||||
MM-MusicActive||interface||||Menu/ru/MusicActive
|
||||
MM-MusicNonActive||interface||||Menu/ru/MusicNonActive
|
||||
MM-LeaveReality||interface||||Menu/ru/LeaveReality
|
||||
CreditsTitle||interface||||Credits/ru/Credits
|
||||
CreditsRoles||interface||Объединитель <br> Укротитель питона <br> Рисователь <br> Композитор||
|
||||
CreditsNames||interface||Кирилл Ивлев <br> Илья Коновалов <br> Дмитрий Уваров <br> Татьяна Степаненко||
|
||||
Achievement-Header||interface||||Achievements/ru/Achievements
|
||||
Achievement-Status-Template||interface||Открыто %Count% из %Total%||
|
||||
ReportTitle||interface||||Report/ru/Title
|
||||
ReportNewWarning||interface||||Report/ru/NewWarning
|
||||
ReportNewButton||interface||||Report/ru/NewButton
|
||||
ReportWipeWarning||interface||||Report/ru/WipeWarning
|
||||
ReportWipeButton||interface||||Report/ru/WipeButton
|
||||
MF-FBV-ReturnButton||interface||||Dashboard/Indicators/ForceBoxes/Ru/Return
|
||||
MF-FBV-OpenBoxButton||interface||||Dashboard/Indicators/ForceBoxes/Ru/OpenBox
|
||||
MF-FBV-AcquireButton||interface||||Dashboard/Indicators/ForceBoxes/Ru/Acquire
|
||||
MF-FBV-NoAdsDeliveredSign||interface||||Dashboard/Indicators/ForceBoxes/Ru/NoAds
|
||||
Universe||interface||||
|
||||
Time|Time|indicator|AboutTheGame|Время|Меняйте скорость как хотите.|
|
||||
Energy|Enr|indicator|EnergyAndDensity|Энергия|Расходуйте на водород и пространство.|
|
||||
Density|Den|indicator|EnergyAndDensity|Плотность|Поддерживайте баланс.|
|
||||
DensityRip|DenR|indicator|EnergyAndDensity|Большой разрыв|Повышайте плотность!|
|
||||
DensityCrunch|DenC|indicator|EnergyAndDensity|Большое сжатие|Понижайте плотность!|
|
||||
BurnabilityIndicator|Bur|indicator|BurnabilityAndExplosiveness|Горючесть|Интенсивность горения звёзд.|
|
||||
ExplosivenessIndicator|Exp|indicator|BurnabilityAndExplosiveness|Взрвычатость|Мощность звёздных взрывов.|
|
||||
AggressionIndicator|Agg|indicator|AggressionAndTrends|Агрессия|Эволюция и конкуренция.|
|
||||
||||1 год в секунду||
|
||||
||||10 лет в секунду||
|
||||
||||100 лет в секунду||
|
||||
||||1 000 лет в секунду||
|
||||
||||10 000 лет в секунду||
|
||||
||||100 000 лет в секунду||
|
||||
||||1 000 000 лет в секунду||
|
||||
||||10 000 000 лет в секунду||
|
||||
||||100 000 000 лет в секунду||
|
||||
||||1 000 000 000 лет в секунду||
|
||||
||logtext||Сводка изменений за последние 100 секунд.<br>Указаны средние значения за год.||
|
||||
ForceNavigationImage||interface||||BigBang/Dashboard/ru/Forces
|
||||
1-1-1-NavigationResourceImage||interface||||BigBang/Dashboard/ru/Resources
|
||||
Space|S|entity|SpaceAndHydrogen|Пространство|Нужно для размещения объектов.|
|
||||
Hydrogen|H|entity|SpaceAndHydrogen|Водород|Нужен для создания облаков и звёзд.|
|
||||
Helium|He|entity|HeliumAndMetals|Гелий|Экономит водород.|
|
||||
Metals|Met|entity|HeliumAndMetals|Металлы|Нужны для создания планет.|
|
||||
||||Металличность||
|
||||
NavigationStarImage||interface||||BigBang/Dashboard/ru/Stars
|
||||
Nebula|N|entity|Clouds|Туманность|Производит водород.|
|
||||
Globula|G|entity|Clouds|Глобула|Производит больше водорода.|
|
||||
StarNursery|STN|entity|Clouds|Звёздная колыбель|Производит водород и звёзды.|
|
||||
MolecularCloud|MC|entity|Clouds|Молекулярное облако|Производит много водорода.|
|
||||
ActiveStarNursery|ASTN|entity|Clouds|Активная звёздная колыбель|Производит очень много водорода и звёзд.|
|
||||
BrownDwarf|BD|entity|DwarfStars|Бурый карлик|Бесполезен, но не занимает место.|
|
||||
RedDwarf|RD|entity|DwarfStars|Красный карлик|Простейшая звезда.|
|
||||
OrangeDwarf|OD|entity|DwarfStars|Оранжевый карлик|Звезда с планетами.|
|
||||
YellowDwarf|YD|entity|DwarfStars|Жёлтый карлик|Звезда похожая на Солнце.|
|
||||
WhiteYellowDwarf|WYD|entity|DwarfStars|Бело-жёлтый карлик|Звезда со множеством планет.|
|
||||
RedGiant|RG|entity|GiantStars|Красный гигант|Деградирующая старая звезда.|
|
||||
YellowGiant|YG|entity|GiantStars|Жёлтый гигант|Оптимальна для производства металлов.|
|
||||
BlueGiant|BG|entity|GiantStars|Голубой гигант|Сбалансированно производит металлы и пространство.|
|
||||
Supergiant|SG|entity|GiantStars|Сверхгигант|Быстро производит пространство и чуть-чуть металлов.|
|
||||
Hypergiant|HG|entity|GiantStars|Гипергигант|Бурно сгорает, создавая много пространства.|
|
||||
WhiteDwarf|WD|entity|DegenerateObjects|Белый карлик|Остаток сгоревшего карлика.|
|
||||
NeutronStar|NS|entity|DegenerateObjects|Нейтронная звезда|Остаток сгоревшего гиганта.|
|
||||
Magnetar|MGT|entity|DegenerateObjects|Магнетар|Остаток большого гиганта.|
|
||||
BlackHole|BH|entity|DegenerateObjects|Чёрная дыра|Результат звёздной катастрофы. Нужна для структур.|
|
||||
SupermassiveBlackHole|SMBH|entity|DegenerateObjects|Сверхмассивная чёрная дыра|Собирается из чёрных дыр. Нужна для галактик.|
|
||||
1-3-1-NavigationStructureImage||interface||||BigBang/Dashboard/ru/Structures
|
||||
StarClusterScattered|SCD|entity|StarClusters|Рассеянное звёздное скопление|Небольшая структура неправильной формы.<br>Пространство: +10%.|
|
||||
StarClusterSmall|SCS|entity|StarClusters|Малое звёздное скопление|Имеет шарообразную форму.<br>Пространство: +20%.|
|
||||
StarClusterMedium|SCM|entity|StarClusters|Среднее звёздное скопление|Заметно издалека.<br>Пространство: +30%.|
|
||||
StarClusterLarge|SCL|entity|StarClusters|Большое звёздное скопление|Производит впечатление.<br>Пространство: +50%.|
|
||||
StarClusterGiant|SCG|entity|StarClusters|Гигантское звёздное скопление|Вызывает трепет.<br>Пространство: +80%.|
|
||||
GalaxyEllipticalDwarf|GED|entity|Galaxies|Карликовая эллиптическая галактика|Размером с Малое Магелланово Облако.<br>Пространство: +100%.|
|
||||
GalaxyEllipticalSmall|GES|entity|Galaxies|Малая эллиптическая галактика|Размером с Большое Магелланово Облако.<br>Пространство: +150%.|
|
||||
GalaxyEllipticalMedium|GEM|entity|Galaxies|Средняя эллиптическая галактика|Размером с Млечный Путь.<br>Пространство: +200%.|
|
||||
GalaxyEllipticalLarge|GEL|entity|Galaxies|Большая эллиптическая галактика|Размером с Туманность Андромеды.<br>Пространство: +300%.|
|
||||
GalaxyEllipticalGiant|GEG|entity|Galaxies|Гигантская эллиптическая галактика|Похожа на IC 1101.<br>Пространство: +450%.|
|
||||
GalaxySpiralDwarf|GSD|entity|Galaxies|Карликовая спиральная галактика|Похожа на Малое Магелланово Облако.|
|
||||
GalaxySpiralSmall|GSS|entity|Galaxies|Малая спиральная галактика|Похожа на Большое Магелланово Облако.|
|
||||
GalaxySpiralMedium|GSM|entity|Galaxies|Средняя спиральная галактика|Похожа на Млечный Путь.|
|
||||
GalaxySpiralLarge|GSL|entity|Galaxies|Большая спиральная галактика|Похожа на Туманность Андромеды.|
|
||||
GalaxySpiralGiant|GSG|entity|Galaxies|Гигантская спиральная галактика|Размером с IC 1101.|
|
||||
GalacticGroup|LGG|entity|LargeStructures|Группа галактик|Объединяет галактики.|
|
||||
GalacticSupercluster|LSC|entity|LargeStructures|Сверхскопление галактик|Основа для структуры вселенной.|
|
||||
GalacticFilament|LGF|entity|LargeStructures|Галактическая нить|Балансирует плотность воидами.|
|
||||
GalacticWall|LGW|entity|LargeStructures|Великая стена|Создаёт структуру вселенной.|
|
||||
UniverseBubble|LUB|entity|LargeStructures|Вселенский пузырь|Больше некуда.|
|
||||
1-4-1-NavigationLifeImage||interface||||BigBang/Dashboard/ru/Planets
|
||||
GasGiant|GG|entity|Planets|Газовый гигант|Слишком велика.|
|
||||
AsteroidBelt|AB|entity|Planets|Астероиды|Куча камней.|
|
||||
RockyPlanet|RP|entity|Planets|Каменистая планета|Не подходит для жизни.|
|
||||
HabitablePlanet|HP|entity|Planets|Обитаемая планета|Имеет потенциал для жизни.|
|
||||
LivingPlanet|LP|entity|Planets|Живая планета|На этой планете есть жизнь!|
|
||||
|Star|||Звезда||
|
||||
|SC|||Звёздное скопление||
|
||||
|GAL|||Галактика||
|
||||
AboutTheGame||help||Об игре|В этой игре вы можете <b>создать вселенную</b> и управлять её развитием. Конечную цель вы определяете для себя самостоятельно. Попробуйте получить те достижения, которые вам понравятся. Читайте подсказки, исследуйте возможности и не бойтесь экспериментировать. Всегда можно начать всё заново.<br><br>Числа больше 1 000 записаны <b>экспоненциально</b>. 1e4 — это 10 000, то есть «единица, у которой 4 нуля». 1е6 — миллион, 1е9 — миллиард и так далее. 13,77е9 — это 13 миллиардов 770 миллионов.<br><br>Игра идёт в реальном времени с изменяемым масштабированием. За одну секунду может пройти год, десять лет, век и так далее. Меняйте скорость течения времени кнопками «−» и «+».|Help/ru/AboutTheGame
|
||||
GameplayBasics||help||Основы игры|Вам доступен большой, но конечный <b>запас энергии</b>, нужной для создания всего вещества во вселенной и самого её пространства. В начале подавляющая часть этой энергии вам напрямую недоступна. Но в ходе игры вы будете забирать всё больше энергии, и чем меньше её останется в запасе, тем сложнее будет брать новую. Нехватка энергии не станет острой проблемой в ближайший миллиард лет или около того: запас действительно очень велик.<br><br>Из энергии с помощью облаков производится вещество в виде <b>водорода</b>. Он тратится на создание новых облаков и звёзд. Звёзды в процессе горения создают <b>пространство</b>, в котором могут быть размещены новые звёзды и облака. Создание пространства также расходует некоторую часть энергии из общего запаса.|Help/ru/GameplayBasics
|
||||
StarsAndStructures||help||Звёзды и структуры|Чем массивнее звезда, тем быстрее она сгорает и больше пространства производит. Самым выгодным вложением водорода на полученное пространство являются самые маленькие звёзды. Но они горят очень долго и медленно производят пространство. Также каждая звезда сама занимает единицу пространства.<br><br>Звёзды и туманности растущей вселенной есть смысл организовывать в галактики и более крупные <b>структуры</b>. В структурах производство пространства звёздами увеличивается.<br><br>Соотношение между веществом и свободным пространством называется <b>плотностью</b>. Вселенная стабильна только в определённом диапазоне плотности. При выходе за его рамки возникают проблемы.|Help/ru/StarsAndStructures
|
||||
LifeAndForces||help||Жизнь и силы|Возле некоторых звёзд возникают <b>планеты</b>, на которых может появиться жизнь. При определённых обстоятельствах жизнь эволюционирует до разумной, а разумная развивается до высокотехнологичной. Далее она пытается освоить вселенную, которую считает своей.<br><br>Многие события во вселенной случаются не всегда, а лишь с некоторой вероятностью. У вас есть доступ к особым <b>силам</b>, с помощью которых вы можете «подкручивать» вероятности интересующих вас событий, влияя на законы природы.|Help/ru/LifeAndForces
|
||||
Achievements||help||Достижения|В ходе игры вам совершенно точно удастся выполнить некоторые <b>достижения</b>. Простые получаются сами собой, а вот над более сложными может понадобиться призадуматься. Можно использовать список достижений в качестве карты подсказок возможных следующих шагов.<br><br>Гнаться за всеми достижениями необязательно. Это ваша вселенная и вы можете делать с ней что угодно. Однако достижение есть достижение, и помимо личной гордости о проделанной работе вы сможете с лёгкостью <b>поделиться</b> им с друзьями!|Help/ru/Achievements
|
||||
Karma||help||Карма|Каждое полученное достижение даёт вам некоторое число <b>кармических очков</b>. При создании новой вселенной вы можете потратить полученные очки на получение небольших бонусов, с помощью которых вам непременно удастся добиться лучших результатов.<br><br>Потраченные кармические очки никуда не деваются: они <b>остаются</b> с вами и вы снова сможете их потратить в следующей вселенной. Скорее всего, к этому моменту их станет ещё больше.|Help/ru/Karma
|
||||
Gauges||help||Индикаторы|На приборной панели расположены <b>индикаторы</b> тех показателей, которые особенно важны для выбранной страницы с объектами.|Help/ru/Gauges
|
||||
EnergyAndDensity||help||Энергия и плотность|<b>Энергия</b> из запаса конвертируется в водород и пространство. Чем ниже доля оставшегося запаса, указанная в середине полоски, тем с меньшей эффективностью работают источники создания ресурсов, такие как облака для водорода и звёзды для пространства.<br>Запас энергии правда очень велик, хоть и не бесконечен.<br><br>Средняя <b>плотность</b> вселенной это отношение общей массы вещества, то есть всех звёзд, облаков и так далее, к пустому пространству. Она растёт вместе с массой и уменьшается с ростом пространства. Если индикатор «ρ» выйдет из «средней зоны» и склонится к одной из крайностей, то последствия могут вас неприятно удивить.|Help/ru/EnergyAndDensity
|
||||
BurnabilityAndExplosiveness||help||Горючесть и взрывчатость|<b>Горючесть</b> показывает текущую скорость горения звёзд. На неё могут влиять плотность вселенной (чем больше, тем выше), действия сверхразвитых цивилизаций и силы игрока.<br><br><b>Взрывчатость</b> показывает текущую вероятность гамма-всплесков и мощность взрыва при любом коллапсе звезды-гиганта. На неё могут влиять плотность вселенной (чем больше, тем ниже), действия сверхразвитых цивилизаций и силы игрока.|Help/ru/BurnabilityAndExplosiveness
|
||||
AggressionAndTrends||help||Агрессия и тенденции|<b>Агрессия</b> показывает интенсивность конкурентной борьбы между видами, цивилизациями и их внутренними фракциями. Она влияет на развитие и стабильность жизни и обществ. Изменяется под воздействием вселенских тенденций и сил игрока.<br>По умолчанию показывается средняя вселенская агрессия. Но можно выделить какой-либо цивилизационный уровень и увидеть среднюю агрессию именно для этого уровня.<br><br><b>Тенденциями</b> называются результаты долговременных действий цивилизаций. Всего их пять.|Help/ru/AggressionAndTrends
|
||||
SpaceAndHydrogen||help||Пространство и водород|Большинство созидательных действий требует траты ресурсов.<br><br><b>Пространство</b> нужно для размещения всего того, что вы создаёте. Для создания нового пространства зажигайте звёзды! Также не забывайте объединять созданные звёзды в структуры, чтобы получать ещё больше пространства.<br><br><b>Водород</b> требуется для создания туманностей и звёзд. Туманности позволяют получать больше водорода. Звёзды создают пространство и используются для базирования планет. Эти объекты также служат кирпичиками для возведения сложных структур.|Help/ru/SpaceAndHydrogen
|
||||
HeliumAndMetals||help||Гелий и металлы|Гелий и металлы остаются, когда звёзды завершают своё существование.<br><b>Гелий</b> не особенно полезен, но от него никуда не деться. Он расходуется автоматически, по возможности замещая водород, экономя более полезный ресурс.<br><br><b>Металлы</b> «загрязняют» ваш запас водорода и гелия более тяжёлыми элементами. Это хорошо, поскольку высокая «металличность», отношение металлов к водороду и гелию, увеличивает вероятность возникновения планет у новых звёзд.<br>Металлы добавляются от сгорания звёзд, особенно больших, но общая металличность может уменьшаться, когда вселенная наполняется новыми объектами из чистого водорода и гелия, такими как туманности и звёзды.|Help/ru/HeliumAndMetals
|
||||
Clouds||help||Облака|Облака нужны для создания водорода, из которого впоследствии можно построить вселенную. Из базовых туманностей можно собирать более сложные облачные структуры.<br><br><b>Туманность:</b> самое простое облако.<br><b>Глобула:</b> даёт водород медленнее туманности, но существует дольше, а также занимает меньше места. Используется в создании более крупных облаков.<br><b>Звёздная колыбель:</b> автоматически создаёт звёзды из части выданного водорода.<br><b>Молекулярное облако:</b> большой и долговременный генератор водорода.<br><b>Активная звёздная колыбель:</b> очень большой автоматический генератор водорода и звёзд.|Help/ru/Clouds
|
||||
DwarfStars||help||Звёзды-карлики|Звёзды-карлики дёшевы, существуют долго и производят пространство эффективно. У них бывают планеты, на которых впоследствии может развиться жизнь.<br><br><b>Бурый карлик:</b> брак, бесполезный побочный эффект от создания звёзд. Они возникают сами и не занимают пространство в отличие от остальных звёзд. Производить их специально нельзя, да и не нужно.<br><b>Красный карлик:</b> экономически выгоден для производства пространства, но работает очень медленно.<br><b>Оранжевый карлик:</b> идеален для развития жизни.<br><b>Жёлтый карлик:</b> подходит для возникновения жизни.<br><b>Бело-жёлтый карлик:</b> имеет самую большую зону обитания, но быстро выгорает.|Help/ru/DwarfStars
|
||||
GiantStars||help||Звёзды-гиганты|Звёзды-гиганты быстро сгорают и производят много пространства. Коллапс в конце существования повреждает планеты у соседних звёзд, но производит металлы.<br><br><b>Красный гигант:</b> деградирующая звезда на закате существования.<br><b>Жёлтый гигант:</b> малая звезда-гигант. Производит много металлов, коллапс слаб.<br><b>Голубой гигант:</b> обычная звезда-гигант. Производит пространство и металлы. Средний коллапс.<br><b>Сверхгигант:</b> огромная звезда-гигант. Живёт недолго, производит множество пространства и немного металлов.<br><b>Гипергигант:</b> экстремальная звезда-гигант. Всегда коллапсирует в чёрную дыру, выделяя множество пространства с сумасшедшими разрушениями.|Help/ru/GiantStars
|
||||
DegenerateObjects||help||Вырожденные объекты|Звёзды, выработавшие своё термоядерное топливо. Они занимают пространство и ничего не производят, но из них можно собирать структуры.<br><br><b>Белый карлик:</b> остаток сгоревшего карлика.<br><b>Нейтронная звезда:</b> исключительно компактный остаток звезды-гиганта.<br><b>Магнетар:</b> самый плотный объект, не являющийся чёрной дырой.<br><b>Чёрная дыра:</b> остаток огромной звезды, прошедшей разрушительный коллапс. Их можно объединять в сверхмассивные чёрные дыры.<br><b>Сверхмассивная чёрная дыра:</b> результат слияния множества чёрных дыр звёздной массы. Необходимы для создания галактик. Растут самостоятельно за счёт прочих звёзд, хотя и очень медленно.|Help/ru/DegenerateObjects
|
||||
StarClusters||help||Звёздные скопления|Звёздные скопления являются самыми скромными из структур. Это некоторое количество звёзд, расположенных близко друг к другу и связанных гравитационно. Подобные комки вносят разнообразие в однородный вселенский ландшафт.<br><br><b>Рассеянное:</b> небольшая структура неправильной формы.<br><b>Малое:</b> имеет шарообразную форму.<br><b>Среднее:</b> заметно издалека.<br><b>Большое:</b> производит впечатление.<br><b>Гигантское:</b> вызывает трепет.<br><br>Звёзды в структурах ведут себя как обычно, но получают бонус к производству пространства. Со временем скопления рассыпаются, высвобождая звёзды. Крупные скопления существуют дольше, чем мелкие, и дают больший бонус пространства.|Help/ru/StarClusters
|
||||
Galaxies||help||Галактики|<b>Эллиптические галактики</b> имеют линзовидную форму и вращаются медленно. Звёзды в них производят больше пространства, чем в спиральных галактиках. Постепенно эллиптические галактики рассыпаются на отдельные звёзды.<br><br><b>Спиральные галактики</b> имеют неоднородную структуру с рукавами и поддерживают активное звездообразование. Они лучше подходят для зарождения и развития жизни. Со временем в спиральных галактиках заканчивается запас газа, они замедляются и превращаются в более стабильные и скучные эллиптические.<br><br>Звёзды, вошедшие в состав галактик, продолжают функционировать как обычно. Они производят пространство, а у карликов могут быть планеты.|Help/ru/Galaxies
|
||||
LargeStructures||help||Большие структуры|<b>Большие структуры</b> не зря так называются: в качестве строительных кирпичиков в них используются целые галактики. Они нужны для организации вселенной на самых больших масштабах. Без них пространство, равномерно заполненное звёздами и галактиками, оказывается слишком однообразным и разреженным, тогда как они создают градиент заполнения с более и менее плотными областями, более подходящий для освоения сверхразвитыми разумными существами.<br><br>Со временем большие структуры рассыпаются на составляющие, но это происходит очень нескоро.|Help/ru/LargeStructures
|
||||
Planets||help||Планеты|Планеты возникают у звёзд-карликов при наличии металлов. В некотором смысле всё это космическое строительство проводится ради тех планет, у которых есть потенциал для зарождения жизни.<br><br><b>Газовый гигант:</b> большая планета с чрезвычайно плотной атмосферой, непригодная для жизни.<br><b>Пояс астероидов:</b> куча камней недостаточной массы, чтобы собраться в планету.<br><b>Каменистая планета:</b> непригодная для жизни планета. Слишком холодно, жарко или не тот химический состав.<br><b>Обитаемая планета:</b> здесь есть жидкая вода и в перспективе может образоваться жизнь.<br><b>Живая планета:</b> обитатели этой планеты подают большие надежды!|Help/ru/Planets
|
||||
Eons||help||Эоны|Жизнь медленно эволюционирует через эоны от самой примитивной до способной к появлению разума. Не всем планетам удаётся довести этот процесс до победного конца, потому что звёзды горят не вечно.<br><br><b>Архей:</b> не сразу бросается в глаза, но на таких планетах полно археев и цианобактерий.<br><b>Палеозой:</b> помимо вездесущих амёб, здесь присутствуют водоросли, губки и даже моллюски.<br><b>Протерозой:</b> папоротники, черви, трилобиты и рыбы.<br><b>Мезозой:</b> голосеменные, ящеры, динозавры и терапсиды.<br><b>Кайнозой:</b> покрытосеменные и разнообразные млекопитающие, некоторые из которых проявляют признаки интеллекта.|Help/ru/Eons
|
||||
Type0Civilizations||help||Цивилизации типа 0|Докосмическая эпоха — от племени до федерации.<br><br><b>ц0.0:</b> нижний палеолит. Приручение огня, каменные орудия.<br><b>ц0.1:</b> средний палеолит. Развитые каменные орудия.<br><b>ц0.2:</b> верхний палеолит. Сложные каменные орудия, искусство.<br><b>ц0.3:</b> мезолит. Лук и стрелы, рыболовство, лодки.<br><b>ц0.4:</b> ранний неолит. Земледелие.<br><b>ц0.5:</b> средний неолит. Скотоводство, керамика.<br><b>ц0.6:</b> поздний неолит. Крупные поселения.<br><b>ц0.7:</b> первые царства медного и бронзового века. Колесо.<br><b>ц0.8:</b> античные империи железного века. Философия.<br><b>ц0.9:</b> промышленная эпоха. Электричество.|Help/ru/Type0Civilizations
|
||||
Type1Civilizations||help||Цивилизации типа 1|Цивилизации, заселяющие родную солнечную систему.<br><br><b>ц1.0:</b> цивилизация, освоившая родную планету. Сверхпроводимость, термоядерный синтез.<br><b>ц1.1:</b> научные базы в родной системе. Пусковая петля, космический лифт.<br><b>ц1.2:</b> полное управление генетикой. Киборгизация.<br><b>ц1.3:</b> промышленная добыча астероидов. Поселения в родной системе.<br><b>ц1.4:</b> сбор вещества из атмосфер газовых гигантов.<br><b>ц1.5:</b> космические города. Пикотехнологии.<br><b>ц1.6:</b> промышленное антивещество. Первые звездолёты.<br><b>ц1.7:</b> терраформинг. Гамма-лазеры.<br><b>ц1.8:</b> космические станции континентального масштаба.<br><b>ц1.9:</b> частичный рой Дайсона.|Help/ru/Type1Civilizations
|
||||
Type2Civilizations||help||Цивилизации типа 2|Цивилизации, ведущие экспансию к иным звёздам.<br><br><b>ц2.0:</b> цивилизация, освоившая энергию родного солнца.<br><b>ц2.1:</b> изменение планетарных орбит.<br><b>ц2.2:</b> управление инерцией.<br><b>ц2.3:</b> сбор вещества со звёзд.<br><b>ц2.4:</b> фемтотехнологии: управление отдельными фермионами.<br><b>ц2.5:</b> деконструкция и сборка планет.<br><b>ц2.6:</b> окончательное переселение с планет в космос.<br><b>ц2.7:</b> освоение ресурсов звёзд-гигантов.<br><b>ц2.8:</b> управление гравитацией.<br><b>ц2.9:</b> перемещение звёзд.|Help/ru/Type2Civilizations
|
||||
Type3Civilizations||help||Цивилизации типа 3|Цивилизации галактического масштаба.<br><br><b>ц3.0:</b> возможна экспансия в группу галактик.<br><b>ц3.1:</b> создание звёзд.<br><b>ц3.2:</b> использование белых карликов.<br><b>ц3.3:</b> создание чёрных дыр из концентрированного излучения.<br><b>ц3.4:</b> возможна экспансия в сверхскопление.<br><b>ц3.5:</b> использование нейтронных звёзд.<br><b>ц3.6:</b> возможна экспансия в галактическую нить.<br><b>ц3.7:</b> использование магнетаров.<br><b>ц3.8:</b> возможна экспансия в великую стену.<br><b>ц3.9:</b> использование чёрных дыр.|Help/ru/Type3Civilizations
|
||||
Type4Civilizations||help||Цивилизации типа 4|Цивилизации вселенского масштаба.<br><br><b>ц4.0:</b> манипуляции со временем.<br><b>ц4.1:</b> возможна экспансия на масштабы всей вселенной.<br><b>ц4.2:</b> использование сверхмассивных чёрных дыр.<br><b>ц4.3:</b> складное пространство.<br><b>ц4.4:</b> поле невероятности.<br><br>И так далее, всё больше и больше, пока есть место.|Help/ru/Type4Civilizations
|
||||
|
@@ -4,6 +4,8 @@ const E = preload("res://world/owniverse/math/exact.gd")
|
||||
static func get_data() -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
for row: Dictionary in CSVLoader.load(DATA).values():
|
||||
for field in ["span", "stars", "progress", "degradation", "colonization", "war", "diplomacy_rate", "worship"]:
|
||||
row[field] = E.of(row[field])
|
||||
if not result.has(row.stage):
|
||||
result[row.stage] = {
|
||||
"home": row.home,
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
class_name OwniverseExtraParamLoader
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
static func get_data() -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
for row: Dictionary in CSVLoader.load(DATA).values():
|
||||
result[row["param name"]] = E.of(row["param export"])
|
||||
return result
|
||||
|
||||
#region Data
|
||||
const DATA := """
|
||||
param name|param value|param export|comment
|
||||
E|1.00E+27|1.00E+27|количество энергии во вселенной
|
||||
G|6.67E-11|6.67E-11|
|
||||
@@ -25,3 +36,5 @@ ForceBox0Weight| 24|24|Вес вероятности появления зелё
|
||||
ForceBox1Weight| 6|6|Вес вероятности появления синего ящика
|
||||
ForceBox2Weight| 2|2|Вес вероятности появления фиолетового ящика
|
||||
ForceBox3Weight| 1|1|Вес вероятности появления оранжевого ящика
|
||||
"""
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://0sukoduq5ghq
|
||||
@@ -1,18 +1,23 @@
|
||||
class_name OwniverseLifeRuleLoader
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
static func get_data() -> Dictionary:
|
||||
return CSVLoader.load(DATA)
|
||||
var result: Dictionary = CSVLoader.load(DATA)
|
||||
for row: Dictionary in result.values():
|
||||
for field in ["span", "progress", "degradation"]:
|
||||
row[field] = E.of(row[field])
|
||||
return result
|
||||
|
||||
#region Data Items
|
||||
|
||||
const DATA := """
|
||||
id|span|progress|degradation|next|fall
|
||||
HP|5E+06|0.00025|0.0025|EON1|RP:1
|
||||
EON1|1E+07|0.00025|0.0025|EON2|HP:100^RP:1
|
||||
EON2|1E+07|0.00025|0.0025|EON3|EON1:1
|
||||
EON3|1E+06|0.00025|0.0025|EON4|EON2:1000^EON1:1
|
||||
EON4|1E+06|0.00025|0.0025|EON5|EON3:1000^EON1:1
|
||||
EON5|5E+05|0.00025|0.0025|C0.0|EON4:2000^EON1:1
|
||||
EON1|1E+06|0.025|0.0025|EON2|HP:100^RP:1
|
||||
EON2|1E+06|0.025|0.0025|EON3|EON1:1
|
||||
EON3|1E+05|0.025|0.0025|EON4|EON2:1000^EON1:1
|
||||
EON4|1E+05|0.025|0.0025|EON5|EON3:1000^EON1:1
|
||||
EON5|5E+05|0.025|0.0025|C0.0|EON4:2000^EON1:1
|
||||
C0.0|2E+03|0.0175|0.025|C0.1|EON5:1
|
||||
C0.1|2E+03|0.0175|0.025|C0.2|C0.0:1
|
||||
"""
|
||||
|
||||
+33
-4
@@ -46,6 +46,8 @@ float. Весь порядок E27 сохраняет целые единицы.
|
||||
|
||||
### Общие окна рождения и эволюции
|
||||
|
||||
Обработка кадров на скоростях 4–6 использует адаптивный шаг 0.1 → 0.2 → 0.5 реальной секунды: накопленное отставание больше двух текущих шагов повышает ступень, меньше половины шага понижает её. Граница ограничивается запрошенным временем, долг не отбрасывается. Скорости 1–3 используют 1/60 секунды. При смене скорости ступень сбрасывается. Сохранённая незавершённая транзакция сначала возобновляется с прежней границей. Кооперативный бюджет кадра остаётся 40 мс. Изменение шага не изменяет ширину временных партий, но может влиять на результаты дискретных расчётов производства и глобальных параметров.
|
||||
|
||||
Окно рождения шириной `w = lifespan / 40` при `lifespan > 10`, иначе `w = lifespan`. Окно превращения сдвинуто на `lifespan`. При частичном списании:
|
||||
|
||||
```
|
||||
@@ -73,10 +75,10 @@ remainder = expected - planets_created
|
||||
GI не хранит накопленные счётчики первичной материи Unity. Поэтому `stellar_environment()` вычисляет среду из текущего физического состава, включая связанные объекты, без суммирующих групп интерфейса. Это приближение для баланса GI, а не побитовое воспроизведение Unity.
|
||||
|
||||
- `M` — свободные и связанные H/He/Met плюс табличная масса конкретных объектов и масса SMBH.
|
||||
- `V` — физическое S плюс табличный объём объектов и `voids * 1e22`.
|
||||
- `V` — физическое S плюс табличный объём объектов и `voids * ExtraParams.VoidSize`.
|
||||
- `Z = max(0, 0.4 * ln(100 * free_Met / M + 0.11) + 0.88)`; при `M = 0` металличность нулевая.
|
||||
- `B = 65536 * f(M)`, где `f(0)=1`, `f(M)=M^0.1` при `0<M<1e10`, иначе `10`.
|
||||
- `density = 1.23456e-7 * sqrt(B + M) / max(1, V)^(1/6)`.
|
||||
- `B = ExtraParams.DA * f(M)`, где `f(0)=1`, `f(M)=M^0.1` при `0<M<1e10`, иначе `10`.
|
||||
- `density = ExtraParams.NPC * sqrt(B + M) / max(1, V)^(1/6)`.
|
||||
- `blast_factor = clamp(free_S / V, 0, 1)`; при нулевом объёме `1`.
|
||||
|
||||
Планетные бонусы сил пока не перенесены. Параметры мира Bur/Exp/Agg имеют собственные сохраняемые шкалы и эффективные множители (см. раздел индикаторов ниже). Металличность для производства фиксируется до расходования ресурсов на звёзды. Среда бласта фиксируется для поколения до добавления его продуктов.
|
||||
@@ -143,7 +145,11 @@ HP деградирует в RP. У эонов возможен откат к п
|
||||
5. При флаге `colonizes` создаются колонии на две стадии ниже отправителя. Цели — существующие GG, AB, RP, HP; колонизация заменяет цель и сохраняет её звёздную привязку. Веса дрейфа колонистов — 960/4/9. Исходная цивилизация не расходуется. Новая партия начинается в текущий игровой момент.
|
||||
6. После применения переходов пересчитываются этики, поклонение (`Σ population × worship`), бласты и ограничения среды. Число операций зависит от числа типов и партий, а не от числа цивилизаций.
|
||||
|
||||
Окна исходных партий сохраняют время переходов; несколько частей одного типа обрабатываются агрегированно между минимальным и максимальным временем. Побочный ущерб неистёкшим партиям применяется в текущий момент. Все остатки округления сохраняются в `civilization_remainders`, счётчики — в `civilization_stats`.
|
||||
На скоростях 1–3 размер временной партии равен сроку жизни / 40. На скоростях 4–6 он равен максимуму этого значения и 100, 1000, 10000 игровых лет соответственно (0,1 реальной секунды). Близкие существующие партии объединяются при следующем шаге: целые количества и остатки изъятия складываются, временной интервал охватывает исходные партии. Это приближает моменты переходов внутри объединённого интервала. События со сроком жизни до 10 лет сохраняют прежние окна. При снижении скорости ранее объединённые партии не разделяются; новые получают меньшие окна. Шаг публикации на скоростях 4–6 ограничен 0,1 реальной секунды; накопленное отставание сохраняется.
|
||||
|
||||
Несколько частей одного типа обрабатываются агрегированно между минимальным и максимальным временем. На скоростях 4–6 новые партии жизни и цивилизаций после перехода создаются на ближайшей следующей границе интервала 100/1000/10000 лет. Начало сетки этих новых партий равно нулю независимо от старого `batch_origins`: округлённое время рождения не попадает обратно в раннюю часть старого окна. Это исключает повторные попытки эволюции той же выходящей популяции внутри её временного интервала; следующие интервалы продолжают обрабатываться. На скоростях 1–3 сохраняются исходные интервалы рождения и последовательные поколения. Звёздные события используют прежнюю схему.
|
||||
|
||||
Побочный ущерб неистёкшим партиям применяется в текущий момент. Все остатки округления сохраняются в `civilization_remainders`, счётчики — в `civilization_stats`.
|
||||
|
||||
### Структуры и периоды
|
||||
|
||||
@@ -206,3 +212,26 @@ Ct не получает часы. Инициализация отсутству
|
||||
При достижении 1 учитываются все целые срабатывания, дробный остаток сохраняется. Только Integration имеет последствия в исходной Unity: равновероятно выбирается Federation (+0,1 и Agg −1), Enlightenment (+0,2 и Agg −1) или Order (+0,1). Генератор сохраняет своё состояние. До 256 событий за шаг выбор выполняется по одному; для больших количеств используется нормальная аппроксимация мультиномиального распределения с сохранением точного суммарного числа событий, чтобы высокие скорости не блокировали кадр. Она может менять последовательность исходов при другом разбиении больших интервалов.
|
||||
|
||||
Federation сохраняется и увеличивается как в Unity, но в исходном коде не применяется к дипломатии. Дополнительное правило для него не придумано. Остальные четыре тенденции имеют счётчики срабатываний и сброс, но не последствия — так же, как в исходной реализации. Параметры и остатки тенденций сохраняются; у существующего сохранения текущего формата без этих полей используются нейтральные значения и нулевые тенденции, без изменения ресурсов или возраста.
|
||||
|
||||
## Дополнительные параметры
|
||||
|
||||
Рабочая таблица встроена в `data/owniverse_extra_param_loader.gd` как `const DATA`,
|
||||
по аналогии с остальными таблицами. В `Global._init()` вызывается get_data() до создания главной сцены:
|
||||
CSVLoader разбирает строки, колонка `param export` преобразуется через E.of.
|
||||
Результат хранится только в `Global.ExtraParams`. После редактирования таблицы
|
||||
требуется перезапуск игры; отдельного CSV и статического кэша загрузчика нет.
|
||||
|
||||
Из таблицы берутся начальная энергия E, добавочная масса DA, коэффициент NPC,
|
||||
размер пустот VoidSize, показатель степени combustibility_bonus для горючести
|
||||
выше единицы, пороги bigrip1–3 и bigcrunch1–3. Интервалы индикатора плотности
|
||||
вычисляются из тех же порогов, без дублирующих числовых констант.
|
||||
Начальная энергия относится к новым вселенным; запас энергии существующей
|
||||
вселенной остаётся частью её сохранения.
|
||||
|
||||
G, C, PI, helium_multiplier, helium_bonus_limit, space_generation, ChatPerTick
|
||||
и параметры ForceBox загружаются, но пока не используются в GI: соответствующие
|
||||
формулы и механики старой Unity-версии здесь отсутствуют. Производство объектов
|
||||
в GI задаётся их собственной таблицей life_product и lifespan.
|
||||
|
||||
`tests/test_extra_params.gd` проверяет загрузку точных чисел и изменение энергии,
|
||||
плотности, объёма пустот, порогов индикатора и бонуса горючести.
|
||||
|
||||
+21
-4
@@ -9,6 +9,10 @@ const ItemDescription = preload("res://world/owniverse/item_description.gd")
|
||||
const WorldIndicators = preload("res://ui/indicators/world_indicators.gd")
|
||||
var world_indicators: RefCounted
|
||||
var indicator_elapsed: float = 0.0
|
||||
var indicator_revision: int = -1
|
||||
var actions_revision: int = -1
|
||||
var descriptions_revision: int = -1
|
||||
var displayed_year: Variant = null
|
||||
var description_targets: Dictionary = {}
|
||||
var description_samples: Dictionary = {}
|
||||
var description_rates: Dictionary = {}
|
||||
@@ -159,6 +163,11 @@ func _update_descriptions(delta: float) -> void:
|
||||
description_elapsed += delta
|
||||
if description_elapsed < 0.25:
|
||||
return
|
||||
if descriptions_revision == owniverse.presentation.revision:
|
||||
var has_rate: bool = false
|
||||
for rate in description_rates.values(): has_rate = has_rate or E.ne(rate, 0)
|
||||
if not has_rate: return
|
||||
descriptions_revision = owniverse.presentation.revision
|
||||
for page in description_targets:
|
||||
var id: String = description_targets[page].id
|
||||
var total = ItemDescription.quantities(owniverse, id).total
|
||||
@@ -347,6 +356,10 @@ func create_new_universe() -> void:
|
||||
g_tween.kill()
|
||||
g_active_items.clear()
|
||||
description_targets.clear()
|
||||
indicator_revision = -1
|
||||
actions_revision = -1
|
||||
descriptions_revision = -1
|
||||
displayed_year = null
|
||||
description_samples.clear()
|
||||
description_rates.clear()
|
||||
description_elapsed = 0.0
|
||||
@@ -380,12 +393,15 @@ func _process(delta: float) -> void:
|
||||
|
||||
owniverse.evolute_frame(delta, get_tree())
|
||||
indicator_elapsed += delta
|
||||
if indicator_elapsed >= 0.1:
|
||||
if indicator_elapsed >= 0.1 and indicator_revision != owniverse.presentation.revision:
|
||||
indicator_elapsed = 0.0
|
||||
world_indicators.update(owniverse)
|
||||
indicator_revision = owniverse.presentation.revision
|
||||
_update_descriptions(delta)
|
||||
set_owniverse_item_value()
|
||||
set_datetime(owniverse.state.time.year)
|
||||
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
|
||||
|
||||
#owniverse.cycle(delta)
|
||||
|
||||
@@ -397,10 +413,11 @@ func _process(delta: float) -> void:
|
||||
# them when selection changes and at a modest cadence instead of every
|
||||
# rendered frame. The simulation itself continues to run every frame.
|
||||
active_actions_elapsed += delta
|
||||
if active_actions_dirty or active_actions_elapsed >= 0.1:
|
||||
if active_actions_dirty or (active_actions_elapsed >= 0.1 and actions_revision != owniverse.presentation.revision):
|
||||
active_actions_elapsed = 0.0
|
||||
active_actions_dirty = false
|
||||
_set_active_page_actions()
|
||||
actions_revision = owniverse.presentation.revision
|
||||
|
||||
|
||||
func _change_speed(delta: int) -> void:
|
||||
|
||||
+109
@@ -1,5 +1,24 @@
|
||||
# Проверки поэтапного переноса из Owniverse.U
|
||||
|
||||
`test_simulation_optimizations.gd` сравнивает прямые формулы дрейфа с прежней
|
||||
матрицей, включая целые количества больше E27 и накопленные остатки.
|
||||
Тестовый ledger сверяет выборочное округление с полным на каждом поколении
|
||||
нагруженной симуляции. `test_ui_updates.gd` проверяет обновление по публикации,
|
||||
выбору объекта и изменению параметров мира, а также пропуск повторных расчётов
|
||||
для неизменившегося состояния.
|
||||
|
||||
Динамический размер партий проверяется в `test_batch_boundaries.gd`: скорости
|
||||
1–6, сохранение больших целых количеств и дробных остатков, повторное
|
||||
укрупнение и снижение скорости. Высокие скорости допускают приближение
|
||||
времени переходов; сравнение со старым состоянием целиком не является
|
||||
проверкой эквивалентности такого режима.
|
||||
|
||||
`test_coarse_generations.gd` проверяет отсутствие повторного перехода C0.0,
|
||||
C0.1 и C0.2 внутри одного интервала, старое смещение сетки партий,
|
||||
продолжение эволюции в следующем интервале, большие целые количества и
|
||||
дробные остатки. `test_frame_evolution.gd` сравнивает целый и разбитый по
|
||||
кадрам расчёт, включая возобновление сохранённого незавершённого шага.
|
||||
|
||||
## Производительность на сохранении
|
||||
|
||||
```sh
|
||||
@@ -205,3 +224,93 @@ Stage вызывает `evolute_frame`: транзакция получает б
|
||||
15.9 FPS (максимальный кадр 98 мс), большие количества 17.5 FPS (106 мс),
|
||||
много партий 16.3 FPS (97 мс).
|
||||
Это FPS отрисовки, а не число завершённых транзакций симуляции в секунду.
|
||||
|
||||
Индекс партий переиспользуется внутри одной транзакции, отдельно для каждого
|
||||
массива партий. Замена массива при изъятии или переход к другому состоянию
|
||||
сбрасывает индекс. Сортировка выполняется при нарушении порядка новых окон,
|
||||
а не после каждого добавления в уже существующее окно. `test_batch_boundaries`
|
||||
проверяет объединение, раннюю вставку, замену массива и изоляцию состояний.
|
||||
На копии текущего сохранения с 785 партиями шесть шагов на скорости 6:
|
||||
среднее время 167 → 145 мс; полное состояние после шести шагов совпало точно.
|
||||
|
||||
Следующий этап исключает арифметическую работу при прибавлении нуля,
|
||||
временные знаменатели при умножении целых блоков и нулевые вклады этик.
|
||||
На той же копии сохранения среднее время шага скорости 6 снизилось с 145
|
||||
до 138 мс; полный результат после шести шагов совпал с исходным состоянием.
|
||||
Тест появления разума задаёт свой progress явно, поскольку проверяет
|
||||
сохранение хозяина планеты, а не актуальное значение коэффициента баланса.
|
||||
|
||||
Следующие шесть оптимизаций: однократное изъятие умирающих партий;
|
||||
поверхностные снимки неизменяемых таблиц связей; пропуск планирования
|
||||
цивилизаций без переходов; кэш нормализованных этик и произведений поклонения;
|
||||
пропуск укрупнения для нуля/одной партии; кэш вероятностей переходов.
|
||||
Кэши принадлежат миру и не входят в сохранение. Изменение количества,
|
||||
этик, коэффициентов правил или модификаторов обнаруживается по значениям.
|
||||
Суммирование поклонения сохраняет прежний порядок дробных операций.
|
||||
`test_simulation_optimizations` проверяет также обновление этих кэшей.
|
||||
|
||||
Полное состояние совпало с реализацией до этих шести изменений после каждого
|
||||
из восьми шагов на скоростях 1, 4 и 6, отдельно на большом фрагментированном
|
||||
сценарии и на диагностической копии сохранения (48 сравнений).
|
||||
На M4 Pro шесть шагов по 0.1 секунды скорости 6: 103.3 мс первый,
|
||||
70.9–76.0 мс последующие; пропускная способность без UI 1.278.
|
||||
Замер 600 кадров с реальным UI: 12.24 FPS, p95 97.16 мс, максимум 104.86 мс,
|
||||
520 завершённых транзакций, отношение обработанного времени к поступившему
|
||||
1.057. Замер включает сокращение накопленного отставания сохранения;
|
||||
это результат конкретного сценария на Mac, а не гарантия для Android.
|
||||
|
||||
Адаптивный шаг на скоростях 4–6 выбирает 0.1, 0.2 или 0.5 реальной секунды.
|
||||
Долг больше двух шагов повышает ступень, меньше половины шага понижает её.
|
||||
Шаг ограничен доступным запрошенным временем. Скорости 1–3 сохраняют 1/60;
|
||||
смена скорости сбрасывает ступень. Возобновляемая транзакция сохраняет точную
|
||||
границу из checkpoint, адаптация применяется к следующей транзакции.
|
||||
Бюджет кадра остаётся 40 мс; ширина временных партий не меняется.
|
||||
`test_frame_evolution` проверяет ступени, гистерезис, сохранение долга,
|
||||
переключение скорости, возобновление и совпадение с прямым расчётом того же шага.
|
||||
На свежей диагностической копии сохранения с долгом 407.79 млн лет:
|
||||
600 кадров реального UI на M4 Pro дали 18.05 FPS (p95 92.91 мс,
|
||||
максимум 122.45 мс), поступило 3.335 млн лет, обработано 6.480 млн:
|
||||
пропускная способность 1.943 вместо 0.787 при фиксированном шаге 0.1.
|
||||
Разбиение времени влияет на дискретные производство и глобальные параметры;
|
||||
адаптивные шаги не обещают совпадения результата с последовательностью 0.1.
|
||||
|
||||
Дипломатический drift использует шесть взвешенных вкладов вместо матрицы
|
||||
пар этик: вес = сумма вкладов + 195 × свой вклад − противоположный вклад.
|
||||
Для колониальной матрицы коэффициенты 9, 951 и 5 соответственно.
|
||||
Целая численность и механизм сохранения ошибок распределения не меняются,
|
||||
но порядок операций над дробными весами отличается от матричного варианта.
|
||||
Тест сравнивает с матрицей на точно представимых коэффициентах и проверяет
|
||||
целочисленное сохранение популяций до 1e40 на десятичных коэффициентах.
|
||||
Пустые популяции пропускают drift; численность выходов суммируется один раз.
|
||||
На профильной копии при шаге 0.5 число вызовов снизилось 1038 → 849
|
||||
за шесть шагов, но время drift осталось около 303 мс суммарно;
|
||||
заметного ускорения полного расчёта в этом сценарии не зафиксировано.
|
||||
|
||||
`test_split_optimizations.gd` сравнивает распределение и все сохранённые
|
||||
остатки с прежним scalar split в последовательных вызовах: пустые ветви,
|
||||
единственная ветвь, равные веса, десятичные веса, положительные/отрицательные
|
||||
остатки, количества до 1e80. Также проверяет grouped mul_div против scalar.
|
||||
Split переиспользует разложение количества и знаменателя между ветвями,
|
||||
кэширует ключи остатков (не более 512 наборов) и получает веса дрифта массивом.
|
||||
Вторая нормализация пропускается только при целых долях с нужной суммой:
|
||||
при дробях даже единичное отношение может изменить округление, поэтому
|
||||
прежний путь сохраняется. Единственная положительная ветвь обрабатывается
|
||||
напрямую только при целом количестве, неотрицательных весах и нулевых остатках.
|
||||
На профильной копии время drift за шесть шагов 0.5 снизилось примерно
|
||||
303 → 287 мс, но общее время осталось около 1.31 секунды.
|
||||
|
||||
Обычный drift сокращает целое количество из отношения
|
||||
count*weight/(200*count), колониальный — из отношения с 1000.
|
||||
Целая часть делится на малый знаменатель; для дроби используются исходные
|
||||
масштабированные числитель и знаменатель, чтобы сохранить float округление.
|
||||
`test_split_optimizations` проверяет совпадение со scalar mul_div до 1e80.
|
||||
Производство новорождённых сохраняет только created_from → amount вместо
|
||||
глубокой копии партий. Повторное мгновенное рождение с теми же сроками,
|
||||
длительностью и сеткой сразу добавляется в найденную партию.
|
||||
Индекс ближайшей границы позволяет повторным проходам пропускать будущие
|
||||
партии; замена массива, добавление и масштабирование сбрасывают индекс.
|
||||
Тест границ проверяет объединение и сброс обоих индексов.
|
||||
Адаптивный шаг дополнительно учитывает сглаженное отношение реальной
|
||||
длительности транзакции к обработанному времени. Если отношение выше 0.85
|
||||
и долг больше одного шага, ступень увеличивается раньше порога двух шагов.
|
||||
Долг и граница сохраняются как прежде; измерения являются временным кэшем.
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
# Все объекты, скорость 6
|
||||
|
||||
Создано скриптом `tests/create_full_benchmark_save.gd`. Пароль нативного сохранения: `qwerty`.
|
||||
Генератор не читает и не перезаписывает сохранение игрока.
|
||||
|
||||
Начальное состояние: 151 запись каталога (включая группы), все 44 стадии правил цивилизаций, все шесть этик на каждой стадии, 1000 когорт (по десять на сущность с временными партиями). У большинства обычных объектов количество равно 6e60, у стадий цивилизаций — 6e20. Пространство и водород имеют количество 1e90, гелий и металлы — 1e80, энергия — 1e100. Возраст — 1e15 лет. Запасов достаточно для требований продвинутых стадий звезд и структур. Оставшиеся сроки жизни когорт покрывают весь срок жизни сущностей, поэтому переходы происходят во время замера.
|
||||
|
||||
Это искусственный экстремальный сценарий, а не прогноз для обычного сохранения.
|
||||
|
||||
Машина: Apple M4 Pro, Godot 4.7.2, рендерер Metal Mobile.
|
||||
|
||||
| Замер | Результат |
|
||||
|---|---:|
|
||||
| Headless, скорость 6, шесть шагов по 0,5 секунды | 943,936–1221,992 мс на шаг |
|
||||
| Среднее время headless-шага | 1122,287 мс |
|
||||
| Пропускная способность headless | 0,446 секунды симуляции на секунду CPU |
|
||||
| Настоящий UI, страница цивилизаций, 600 кадров | 17,38 FPS |
|
||||
| Средний / p95 / максимальный кадр UI | 57,55 / 99,78 / 116,94 мс |
|
||||
| Завершенные транзакции | 23 |
|
||||
| Запрошенный прирост игрового времени | 3464662,267 лет |
|
||||
| Опубликованный прирост игрового времени | 1080000 лет |
|
||||
| Пропускная способность с UI | 0,3117 |
|
||||
| Накопленный долг за замер | 2384662,267 игровых лет (23,85 реальных секунд на скорости 6) |
|
||||
| Записей каталога осталось в конце | 151 |
|
||||
|
||||
Динамический шаг достигает предела в 0,5 секунды. Рендеринг идет быстрее 5 FPS, но симуляция не успевает за заданным темпом в этом сценарии. На обработку поколений приходится примерно 94% времени CPU. Подготовка цивилизаций занимает в среднем 634 мс на шаг, в том числе дрифт этик — 218 мс, войны — 124 мс; колонизация — 121 мс на шаг. Интервалы профилировщика вложенные и частично перекрываются.
|
||||
|
||||
## Сравнение оптимизации дрифта (2026-10-05)
|
||||
|
||||
Замеры сделаны на одном сгенерированном сохранении и одной машине до и после прямого распределения потоков для обычного и колониального дрифта, а также кеширования ставок дипломатии в фиксированной точке. Каждый headless-запуск выполняет шесть последовательных шагов по 0,5 секунды на каждой скорости.
|
||||
|
||||
| Замер | До | После |
|
||||
|---|---:|---:|
|
||||
| Средний headless-шаг на скорости 6 | 1122,287 мс | 1018,559 мс |
|
||||
| Пропускная способность headless на скорости 6 | 0,446 | 0,491 |
|
||||
| Дрифт этик, сумма интервалов за шесть шагов | 1,338 с | 0,643 с |
|
||||
| UI, FPS за 600 кадров | 17,38 | 17,04 |
|
||||
| Средний / p95 / максимальный кадр UI | 57,55 / 99,78 / 116,94 мс | 58,70 / 102,67 / 114,30 мс |
|
||||
| Завершенные UI-транзакции | 23 | 25 |
|
||||
| Запрошено / опубликовано игровых лет в UI | 3464662 / 1080000 | 3533627 / 1180000 |
|
||||
| Пропускная способность симуляции с UI | 0,3117 | 0,3339 |
|
||||
|
||||
Средний headless-шаг ускорился примерно на 9,2%, а интервал профиля дрифта этик сократился примерно на 52%. Частота кадров UI практически не изменилась (в этом запуске снизилась примерно на 2%), при этом завершилось больше транзакций и выросла доля опубликованного игрового времени. На скорости 6 стресс-сценарий все еще отстает от реального времени: пропускная способность — около 0,49 секунды симуляции на секунду CPU в headless и 0,33 с UI. Самыми затратными остаются планирование поколений и войны.
|
||||
|
||||
Прямые пути сохраняют целое население, но разносят малые целочисленные потоки по нескольким последовательным переходам с помощью остатков для каждого источника. Поэтому распределение в отдельном переходе отличается от независимого округленного распределения по матрице 6×6. Ставки дипломатии округляются до сетки фиксированной точки с шагом 1e-9. Целевая регрессия: 769 проверок эволюции цивилизаций, оптимизаций симуляции, границ партий, укрупненных поколений и эволюции кадров; все пройдены.
|
||||
|
||||
## Последующая оптимизация подготовки поколений и кешей (2026-10-05)
|
||||
|
||||
На той же нагрузке среднее время headless-шага на скорости 6 снизилось с 1018,559 до 1005,138 мс (примерно на 1,3%). Пропускная способность headless изменилась с 0,491 до 0,497. Суммарные интервалы `generations` близки: 5,703 с и 5,629 с за шесть шагов. Подготовка цивилизаций остается самой затратной измеренной подфазой — 3,130 с. UI также почти не изменился: 17,04 → 17,09 FPS, пропускная способность симуляции — 0,3339 → 0,3351. Улучшение небольшое: арифметика населения в поколениях по-прежнему дорога при сотнях активных когорт и множестве правил цивилизаций.
|
||||
|
||||
Профиль теперь отдельно показывает `generation_withdrawal` и `generation_products`. Первый интервал занимает 55,5 мс, второй — 120,2 мс за шесть headless-шагов на скорости 6. Эти значения нельзя складывать с общим временем поколений, поскольку интервалы профилировщика вложенные.
|
||||
|
||||
## Инкрементальная обработка партий новорожденных (2026-10-05)
|
||||
|
||||
Удалены снимки когорт до и после рождения и последующий полный обход когорт. `append_batch` теперь сразу накапливает поправку на возраст новорожденных при добавлении частей когорт. Финальный замер после повторного использования сумм завершившихся партий в пределах поколения дал 1028,991 мс на headless-шаг скорости 6 и пропускную способность 0,486. В повторных прогонах этой же версии получилось 1005–1011 мс, поэтому при выборке всего в шесть шагов разброс велик, и устойчивое ускорение утверждать нельзя. В UI получилось 17,05 FPS, среднее время кадра 58,66 мс и пропускная способность 0,3342. Новый профиль показывает 319 мс на обработку партий новорожденных за шесть шагов. Это изменение пока не демонстрирует снижения общего времени на этом сохранении; его потенциальная польза относится прежде всего к сценариям с гораздо большим числом когорт на сущность, где прежние обходы росли пропорционально числу когорт.
|
||||
|
||||
Повторить замеры из родительской папки проекта:
|
||||
|
||||
```sh
|
||||
godot --headless --path Owniverse.GI --script res://tests/create_full_benchmark_save.gd
|
||||
godot --headless --path Owniverse.GI --script res://tests/benchmark_saved_state.gd -- Owniverse.GI/tests/artifacts/all_objects_speed6.adventure.json 0.5 profile
|
||||
godot --path Owniverse.GI --script res://tests/benchmark_full_ui.gd
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
func _initialize(): 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 k in value: value[k] = restore(value[k])
|
||||
elif value is Array:
|
||||
for i in value.size(): value[i] = restore(value[i])
|
||||
return value
|
||||
func run():
|
||||
var payload = E.decode(restore(JSON.parse_string(FileAccess.get_file_as_string("res://tests/artifacts/all_objects_speed6.adventure.json"))))
|
||||
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.state = payload.state.duplicate(true)
|
||||
stage.owniverse.restore_frame_checkpoint(payload.get("simulation", {}))
|
||||
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog)
|
||||
stage.set_owniverse_item_value()
|
||||
stage.stage_page_scroller.scroll_horizontal = 4 * stage.PAGE_WIDTH
|
||||
var initial_requested: Variant = stage.owniverse.frame_clock.get("datetime", stage.owniverse.state.time.datetime)
|
||||
var initial_published: Variant = stage.owniverse.state.time.datetime
|
||||
stage.owniverse.profile_enabled = true
|
||||
stage.measuring = true
|
||||
for i in 600: await process_frame
|
||||
stage.measuring = false
|
||||
var samples = stage.frame_samples
|
||||
var total: float = 0
|
||||
for sample in samples: total += sample
|
||||
samples.sort()
|
||||
print("Saved UI: FPS=%.2f mean=%.2fms p95=%.2fms max=%.2fms commits=%d" % [1000.0/(total/samples.size()),total/samples.size(),samples[int(ceil(samples.size()*0.95))-1],samples.back(),stage.owniverse.frame_commits])
|
||||
var requested_advance: Variant = E.subtract(stage.owniverse.frame_clock.datetime, initial_requested)
|
||||
var published_advance: Variant = E.subtract(stage.owniverse.state.time.datetime, initial_published)
|
||||
print("Requested years advanced=", requested_advance, " published=", published_advance, " throughput=", E.divide(published_advance, requested_advance))
|
||||
print("UI CPU profile: ", stage.owniverse.profile)
|
||||
var remaining_objects: int = 0
|
||||
for id in stage.owniverse.catalog.entities:
|
||||
if E.gt(stage.owniverse.ledger.physical_amount(stage.owniverse.state, id), 0): remaining_objects += 1
|
||||
print("Remaining objects: ", remaining_objects)
|
||||
print("Final adaptive step: ", stage.owniverse.frame_step_seconds)
|
||||
for entity in stage.owniverse.catalog.entities.values(): entity.free()
|
||||
stage.owniverse.free()
|
||||
scene.free()
|
||||
for i in 3: await process_frame
|
||||
quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://3xjh23kg5vrn
|
||||
@@ -27,6 +27,8 @@ func run():
|
||||
quit(1)
|
||||
return
|
||||
var initial: Dictionary = E.decode(restore_json_integers(data)).state
|
||||
var validation: String = root.get_node("SaveManager").validate_data(E.decode(restore_json_integers(data)))
|
||||
print("Save validation: ", "OK" if validation.is_empty() else validation)
|
||||
var delta: float = args[1].to_float() if args.size() > 1 else 1.0 / 60.0
|
||||
if not is_finite(delta) or delta <= 0:
|
||||
quit(1)
|
||||
@@ -36,12 +38,18 @@ func run():
|
||||
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
||||
world.state = initial.duplicate(true)
|
||||
world.speed = speed
|
||||
world.profile_enabled = args.has("profile")
|
||||
var elapsed: Array = []
|
||||
for cycle in 6:
|
||||
var start: int = Time.get_ticks_usec()
|
||||
world.evolute(delta)
|
||||
elapsed.append((Time.get_ticks_usec() - start) / 1000.0)
|
||||
print("Speed %d, six consecutive %s-second steps, ms: %s" % [speed, delta, elapsed])
|
||||
var total_ms: float = 0.0
|
||||
for sample in elapsed: total_ms += sample
|
||||
var throughput: float = 6.0 * delta / (total_ms / 1000.0)
|
||||
print("Simulation throughput: %.3f real-time seconds processed per CPU second; needs >= 1.0 to keep up" % throughput)
|
||||
if world.profile_enabled: print("CPU profile (usec, calls): ", world.profile)
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
quit()
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
extends SceneTree
|
||||
## Reproducible stress save; never writes to the player's user:// directory.
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const State = preload("res://world/owniverse/civilization_state.gd")
|
||||
func _initialize(): call_deferred("run")
|
||||
func run():
|
||||
var global: Node = root.get_node("Global")
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.catalog.load_definitions(global.OwniverseEntities)
|
||||
world.speed = 6
|
||||
world.state.time = {"datetime": 1000000000000000, "year": 1000000000000000, "tick_remainder": 0}
|
||||
world.state.energy = E.of("1e100")
|
||||
for id in world.catalog.entities:
|
||||
world.state.amount[id] = E.of("6e60")
|
||||
world.state.stats.total_amount[id] = world.state.amount[id]
|
||||
if global.CivilizationRules.has(id): world.state.amount[id] = E.of("6e20")
|
||||
world.state.amount.S = E.of("1e90")
|
||||
world.state.amount.H = E.of("1e90")
|
||||
world.state.amount.He = E.of("1e80")
|
||||
world.state.amount.Met = E.of("1e80")
|
||||
world.ledger.refresh_groups(world.state)
|
||||
State.enforce_capacity(world, world.state, global.CivilizationRules, world.state.time.year)
|
||||
State.reconcile(world, world.state, global.CivilizationRules)
|
||||
var cohorts: int = 0
|
||||
for id in world.catalog.entities:
|
||||
if world.catalog.is_group(id): continue
|
||||
var lifespan: Variant = State.effective_span(world, world.state, id, global.CivilizationRules)
|
||||
if E.le(lifespan, 0): continue
|
||||
world.state.batches[id] = []
|
||||
var width: Variant = E.divide(lifespan, 10)
|
||||
for i in 10:
|
||||
var start: Variant = E.add(world.state.time.year, E.multiply(width, i))
|
||||
var end: Variant = E.add(start, width)
|
||||
var birth: Variant = E.subtract(start, lifespan)
|
||||
world.state.batches[id].append({"amount": E.divide(world.state.amount[id], 10), "created_from": birth, "created_to": E.add(birth, width), "start_year": start, "end_year": end})
|
||||
cohorts += 1
|
||||
world.state.batch_origins[id] = world.state.batches[id][0].created_from
|
||||
world.state.stats.total_amount = world.state.amount.duplicate(true)
|
||||
var manager: Node = root.get_node("SaveManager")
|
||||
var folder: String = ProjectSettings.globalize_path("res://tests/artifacts")
|
||||
DirAccess.make_dir_recursive_absolute(folder)
|
||||
manager.save_directory = folder
|
||||
global.save_plaintext_copy = true
|
||||
var success: bool = manager.save_data("all_objects_speed6", "qwerty", {"state": world.state, "simulation": world.frame_checkpoint(), "ui": {"current_page": 4}})
|
||||
var empty: Array = []
|
||||
for id in world.catalog.entities:
|
||||
if E.le(world.state.amount.get(id, 0), 0): empty.append(id)
|
||||
print("Full save: objects=%d civilization stages=%d cohorts=%d empty=%s success=%s" % [world.catalog.entities.size(), global.CivilizationRules.size(), cohorts, empty, success])
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
quit(0 if success and empty.is_empty() else 1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://1icskkk5nld8
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
extends "res://world/owniverse/object_ledger.gd"
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func round_object_allocations(target: Dictionary, affected: Variant = null) -> void:
|
||||
var reference: Dictionary = target.duplicate(true)
|
||||
super.round_object_allocations(reference)
|
||||
super.round_object_allocations(target, affected)
|
||||
checks += 1
|
||||
if not E.eq(target, reference):
|
||||
failures += 1
|
||||
push_error("Selective rounding differs from full rounding")
|
||||
+1
@@ -0,0 +1 @@
|
||||
uid://csew6jyuiwxh0
|
||||
Vendored
+8
@@ -1,4 +1,12 @@
|
||||
extends "res://stage/stage.gd"
|
||||
var actions_refreshes: int = 0
|
||||
|
||||
func tick_ui(delta: float) -> void:
|
||||
super._process(delta)
|
||||
|
||||
func _set_active_page_actions() -> void:
|
||||
actions_refreshes += 1
|
||||
super._set_active_page_actions()
|
||||
|
||||
# Keep scene wiring and display logic, without accessing the player's save.
|
||||
func load_data() -> bool:
|
||||
|
||||
@@ -38,6 +38,43 @@ func dispose(world) -> void:
|
||||
|
||||
func run() -> void:
|
||||
var world = fixture()
|
||||
world.catalog.entities.N.lifespan = 100
|
||||
world.batches.append_batch(world.state, "N", 2, 10, 10)
|
||||
world.batches.append_batch(world.state, "N", 3, 10, 10)
|
||||
check(world.state.batches.N.size() == 1 and E.eq(world.state.batches.N[0].amount, 5), "Repeated insertion merges using the cohort index")
|
||||
world.batches.append_batch(world.state, "N", 1, 0, 0)
|
||||
check(E.eq(world.state.batches.N[0].created_from, 0), "Earlier insertion preserves chronological order")
|
||||
var replaced: Array = world.state.batches.N.duplicate(true)
|
||||
world.state.batches.N = replaced
|
||||
world.batches.append_batch(world.state, "N", 7, 10, 10)
|
||||
check(E.eq(replaced[1].amount, 12), "Replacing cohort array invalidates cached references")
|
||||
var separate: Dictionary = world.state.duplicate(true)
|
||||
world.batches.append_batch(separate, "N", 9, 10, 10)
|
||||
check(E.eq(separate.batches.N[1].amount, 21) and E.eq(world.state.batches.N[1].amount, 12), "Index is isolated between transaction states")
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
for speed in range(1, 7):
|
||||
world.state.speed = speed
|
||||
check(E.eq(world.batches.window_duration(world.state, 100), 2.5 if speed < 4 else pow(10, speed - 2)), "Windows follow selected speed")
|
||||
check(E.eq(world.batches.window_duration(world.state, 5), 5), "Short explosions retain their timing")
|
||||
var huge: Variant = E.of("10000000000000000000000000000000000000000")
|
||||
world.catalog.entities.N.lifespan = 100
|
||||
world.state.batches.N = [
|
||||
{"amount": huge, "start_year": 100, "end_year": 110, "created_from": 0, "created_to": 10, "withdraw_remainder": 0.25},
|
||||
{"amount": 7, "start_year": 120, "end_year": 130, "created_from": 20, "created_to": 30, "withdraw_remainder": 0.5}
|
||||
]
|
||||
world.batches.coarsen(world.state)
|
||||
check(world.state.batches.N.size() == 1, "High speed combines nearby cohorts")
|
||||
check(E.eq(world.state.batches.N[0].amount, E.add(huge, 7)), "Coarsening preserves exact large whole counts")
|
||||
check(E.eq(world.state.batches.N[0].withdraw_remainder, 0.75), "Coarsening preserves withdrawal carry")
|
||||
var combined: Dictionary = world.state.batches.N[0].duplicate(true)
|
||||
world.batches.coarsen(world.state)
|
||||
check(world.state.batches.N[0] == combined, "Coarsening is idempotent")
|
||||
world.state.speed = 1
|
||||
world.batches.coarsen(world.state)
|
||||
check(world.state.batches.N[0] == combined, "Slowing down does not invent finer history")
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
world.state.batch_origins.LGRB = E.of("10357034.80000000074505806")
|
||||
var boundary: Variant = E.of("41215289.79999999701976776")
|
||||
for interval in [[E.subtract(boundary, E.of("2.5")), E.add(boundary, E.of("2.25"))], [boundary, E.add(boundary, 2)]]:
|
||||
@@ -54,5 +91,23 @@ func run() -> void:
|
||||
check(E.eq(total, 100), "Boundary correction preserves all objects")
|
||||
check(E.le(world.state.batches.LGRB.size(), 7), "Finite number of windows")
|
||||
dispose(world)
|
||||
world = fixture()
|
||||
world.batches.append_batch(world.state, "N", 100, 100, 100)
|
||||
var first: Dictionary = world.state.batches.N[0].duplicate(true)
|
||||
world.batches.append_batch(world.state, "N", 7, 100, 100)
|
||||
check(world.state.batches.N.size() == 1 and E.eq(world.state.batches.N[0].amount, 107), "Repeated births merge directly into one window")
|
||||
check(E.eq(world.state.batches.N[0].start_year, first.start_year), "Direct merge preserves transition time")
|
||||
check(world.batches.withdraw_batches(world.state, "N", 0).is_empty(), "Future cohorts are not withdrawn")
|
||||
var untouched: Array = world.state.batches.N
|
||||
check(world.batches.withdraw_batches(world.state, "N", 1).is_empty() and is_same(untouched, world.state.batches.N), "Readiness index skips future cohorts")
|
||||
world.state.batches.N = [{"amount": 7, "start_year": 0, "end_year": 0, "created_from": 0, "created_to": 0}]
|
||||
check(world.batches.withdraw_batches(world.state, "N", 1).size() == 1, "Replacing cohorts invalidates readiness index")
|
||||
world.batches.append_batch(world.state, "N", 3, 100, 100)
|
||||
check(E.eq(world.state.batches.N[0].amount, 3), "Replacing cohorts invalidates instant merge cache")
|
||||
world.batches.withdraw_batches(world.state, "N", 0)
|
||||
world.batches.scale_batches(world.state, "N", 0)
|
||||
world.batches.withdraw_batches(world.state, "N", 1)
|
||||
check(world.state.batches.N.is_empty(), "Scaling invalidates readiness and removes zero cohorts")
|
||||
dispose(world)
|
||||
print(("Batch boundaries: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func _initialize(): call_deferred("run")
|
||||
func check(condition: bool, message: String):
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
|
||||
func fixture(id: String, speed: int):
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
||||
for entity in world.catalog.entities.values():
|
||||
world.state.amount[entity.id] = 0
|
||||
world.state.stats.total_amount[entity.id] = 0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.state.amount.RD = 1
|
||||
world.state.binds.RD = {id: 40000}
|
||||
world.state.batches[id] = [{"amount": 40000, "start_year": 1, "end_year": 1}]
|
||||
# A saved origin off the new grid must not backdate rounded successors.
|
||||
world.state.batch_origins[id] = 500
|
||||
world.speed = speed
|
||||
world.profile_enabled = true
|
||||
return world
|
||||
|
||||
func dispose(world):
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
|
||||
func run():
|
||||
for id in ["C0.0", "C0.1", "C0.2"]:
|
||||
var world = fixture(id, 6)
|
||||
world.evolution.evolute_to_datetime(10000)
|
||||
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
||||
var population: Variant = 0
|
||||
for stage in physical:
|
||||
if world.catalog.entities[stage].group_as == "Planet":
|
||||
population = E.add(population, physical[stage])
|
||||
check(E.eq(population, 40000), "Coarse transitions preserve all whole populations: " + id)
|
||||
for batches: Array in world.state.batches.values():
|
||||
for batch: Dictionary in batches:
|
||||
check(E.gt(batch.start_year, 10000), "Successors cannot expire inside their birth bucket: " + id)
|
||||
if id == "C0.0":
|
||||
check(E.eq(physical.get("C0.0", 0), 38300), "C0.0 attempts evolution once")
|
||||
check(E.eq(physical.get("C0.1", 0), 700), "C0.0 progress rate is applied once")
|
||||
check(E.eq(physical.get("EON5", 0), 1000), "C0.0 degradation rate is applied once")
|
||||
elif id == "C0.1":
|
||||
check(E.eq(physical.get("C0.1", 0), 38300), "C0.1 attempts evolution once")
|
||||
check(E.eq(physical.get("C0.2", 0), 700), "C0.1 successor does not immediately attempt again")
|
||||
else:
|
||||
check(world.profile.civilization_transitions[1] == 1, "Ethical civilization is processed only once per birth bucket")
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
world.evolution.evolute_to_datetime(10000)
|
||||
check(E.eq(world.state, before), "Repeating completed clock does not repeat transitions")
|
||||
var validation: String = root.get_node("SaveManager").validate_data({"format_version": 5, "app_version": "", "ui": {"current_page": 0}, "state": world.state})
|
||||
check(validation.is_empty(), "Coarse state validates: " + validation)
|
||||
world.evolution.evolute_to_datetime(20000)
|
||||
if id in ["C0.0", "C0.1"]:
|
||||
check(E.lt(world.ledger.physical_amounts(world.state)[id], 38300), "The next bucket still processes legitimate evolution: " + id)
|
||||
dispose(world)
|
||||
var fine = fixture("C0.0", 1)
|
||||
fine.evolution.evolute_to_datetime(10000)
|
||||
check(E.lt(fine.ledger.physical_amounts(fine.state)["C0.0"], 38300), "Low speeds retain multiple chronological generations")
|
||||
dispose(fine)
|
||||
var large = fixture("C0.0", 6)
|
||||
var count: Variant = E.of("10000000000000000000000000000000000000001")
|
||||
large.state.binds.RD["C0.0"] = count
|
||||
large.state.batches["C0.0"][0].amount = count
|
||||
var reference: Dictionary = large.state.duplicate(true)
|
||||
var products: Dictionary = large.life.life_products(reference, large.catalog.entities["C0.0"], count)
|
||||
large.evolution.evolute_to_datetime(10000)
|
||||
var physical: Dictionary = large.ledger.physical_amounts(large.state)
|
||||
for id in products:
|
||||
check(E.eq(physical.get(id, 0), products[id]), "Large whole population undergoes one transition: " + id)
|
||||
check(E.eq(large.state.life_remainders, reference.life_remainders), "Coarse timing preserves the single attempt's fractional carry")
|
||||
dispose(large)
|
||||
print("Coarse generations: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ggml5q061lwh
|
||||
@@ -31,6 +31,10 @@ func run():
|
||||
verify(not E.eq(E.of("1e1000"), INF), "huge number is not infinity")
|
||||
verify(E.eq(E.floor_sum(huge, 2, 1, 2), E.divide(E.multiply(huge, E.subtract(huge, 1)), 2)), "floor sum supports huge window counts")
|
||||
var rows = JSON.parse_string(FileAccess.get_file_as_string("res://tests/fixtures/big_integer_oracle.json"))
|
||||
for row in rows.slice(0, 20):
|
||||
var scaled = B.divmod(B.parse(row[0] + "000000000000000000"), B.parse(row[1] + "000000000000000000"))
|
||||
verify(B.decimal(scaled[0]) == row[4], "cancel shared blocks preserves quotient")
|
||||
verify(B.compare(scaled[1], B.multiply(B.parse(row[5]), B.parse("1000000000000000000"))) == 0, "cancel shared blocks restores original remainder")
|
||||
for row in rows:
|
||||
var a: Array = B.parse(row[0])
|
||||
var b: Array = B.parse(row[1])
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Extra = preload("res://data/owniverse_extra_param_loader.gd")
|
||||
const Parameters = preload("res://world/owniverse/world_parameters.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
func _initialize(): call_deferred("run")
|
||||
func check(ok: bool, label: String):
|
||||
checks += 1
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error(label)
|
||||
func run():
|
||||
var global = root.get_node("Global")
|
||||
check(E.eq(global.ExtraParams, Extra.get_data()), "Global table equals loader data")
|
||||
check(E.eq(global.ExtraParams.E, E.of("1e27")), "Energy parsed as exact integer")
|
||||
check(E.eq(global.ExtraParams.DA, 65536), "Export column is used, not formatted display column")
|
||||
var saved: Dictionary = global.ExtraParams.duplicate()
|
||||
global.ExtraParams.E = E.of("1e40")
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.catalog.load_definitions(global.OwniverseEntities)
|
||||
check(E.eq(world.state.energy, E.of("1e40")), "New world uses configured energy")
|
||||
var initial: Dictionary = world.environment.stellar_environment(world.state)
|
||||
global.ExtraParams.DA = E.multiply(saved.DA, 4)
|
||||
check(E.near(world.environment.stellar_environment(world.state).density, E.multiply(initial.density, 2)), "Base mass changes density")
|
||||
global.ExtraParams.DA = saved.DA
|
||||
global.ExtraParams.NPC = E.multiply(saved.NPC, 2)
|
||||
check(E.near(world.environment.stellar_environment(world.state).density, E.multiply(initial.density, 2)), "NPC changes density")
|
||||
global.ExtraParams.NPC = saved.NPC
|
||||
world.state.amount.S = E.of("1e24")
|
||||
world.state.amount.LGF = 1
|
||||
var old_density = world.environment.stellar_environment(world.state).density
|
||||
global.ExtraParams.VoidSize = E.multiply(saved.VoidSize, 100)
|
||||
check(E.lt(world.environment.stellar_environment(world.state).density, old_density), "Void volume uses configured size")
|
||||
global.ExtraParams.bigcrunch1 = E.of("0.5")
|
||||
global.ExtraParams.bigcrunch2 = E.of("0.6")
|
||||
global.ExtraParams.bigcrunch3 = E.of("0.7")
|
||||
check(Parameters.density_display(E.of("0.6")).mode == "DensityCrunch", "Configured crunch start")
|
||||
check(E.near(Parameters.density_display(E.of("0.6")).position, E.of("0.5")), "Crunch indicator uses configured interval")
|
||||
check(Parameters.density_warning_color(E.of("0.6")) == Color.html("20b0dfbf"), "Configured middle crunch warning")
|
||||
global.ExtraParams.bigrip1 = E.of("0.01")
|
||||
global.ExtraParams.bigrip2 = E.of("0.001")
|
||||
global.ExtraParams.bigrip3 = E.of("0.0001")
|
||||
check(Parameters.density_display(E.of("0.001")).mode == "DensityRip", "Configured rip start")
|
||||
check(E.near(Parameters.density_display(E.of("0.001")).position, E.of("0.5")), "Rip indicator uses configured interval")
|
||||
check(Parameters.density_warning_color(E.of("0.001")) == Color.html("df2020bf"), "Configured middle rip warning")
|
||||
world.state.world_parameters.Bur = 19
|
||||
global.ExtraParams.combustibility_bonus = 1
|
||||
check(E.eq(Parameters.production(world.state, world.catalog.entities.RD), Parameters.effective(world.state, "Bur")), "Configured combustion exponent")
|
||||
global.ExtraParams.merge(saved, true)
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
print("Extra params: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://doertyqlkrm26
|
||||
@@ -96,5 +96,25 @@ func run():
|
||||
invalid.evolute(1e308)
|
||||
check(not invalid.frame_busy and invalid.frame_clock.is_empty() and E.eq(before, invalid.state), "Overflowing elapsed float cannot enter either clock API")
|
||||
dispose(invalid)
|
||||
var adaptive = fixture()
|
||||
adaptive.frame_clock = Clock.advance(adaptive.state.time, 10.0, 6)
|
||||
var debt_clock: Dictionary = adaptive.frame_clock.duplicate(true)
|
||||
check(adaptive.update_frame_step() == 0.2, "Growing debt increases step to 0.2 seconds")
|
||||
check(adaptive.update_frame_step() == 0.5, "Large debt increases step to bounded 0.5 seconds")
|
||||
check(adaptive.update_frame_step() == 0.5 and E.eq(adaptive.frame_clock, debt_clock), "Maximum step preserves requested time")
|
||||
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.3, 6)
|
||||
check(adaptive.update_frame_step() == 0.5, "Hysteresis keeps step between thresholds")
|
||||
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.05, 6)
|
||||
check(adaptive.update_frame_step() == 0.2, "Small debt reduces step")
|
||||
check(adaptive.update_frame_step() == 0.1, "Caught-up simulation returns to base step")
|
||||
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.15, 6)
|
||||
adaptive._frame_cost_ratio = 1.1
|
||||
check(adaptive.update_frame_step() == 0.2, "Measured slow transactions increase step before two-step debt")
|
||||
adaptive.frame_clock = Clock.advance(adaptive.state.time, 0.05, 6)
|
||||
adaptive.speed = 3
|
||||
check(adaptive.update_frame_step() == 1.0 / 60.0, "Low speeds retain 60 Hz scheduling")
|
||||
adaptive.speed = 6
|
||||
check(adaptive.update_frame_step() == 0.1, "Speed switch resets adaptive step")
|
||||
dispose(adaptive)
|
||||
print("Frame evolution: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
|
||||
@@ -66,6 +66,8 @@ func run() -> void:
|
||||
world = fixture()
|
||||
world.state.amount.RD = 1
|
||||
world.state.binds.RD = {"EON5": 40000}
|
||||
# Test host preservation with a fixed rate, independent of game balance.
|
||||
world.catalog.entities.EON5.life_rule.progress = "0.00025"
|
||||
world.evolution.initialize_life_batches()
|
||||
world.evolution.evolve_batches(world.state, E.add(500000, E.divide(500000, 40)))
|
||||
check(E.eq(world.state.binds.RD["C0.0"], 10), "Intelligence emerges at the same host star")
|
||||
|
||||
@@ -30,6 +30,9 @@ func run() -> void:
|
||||
for id in requests: requests[id] = E.multiply(requests[id], E.of("1e40"))
|
||||
var limit: Variant = E.multiply(rng.randi_range(0, 600), E.of("1e40")) if sample % 3 == 0 else rng.randi_range(0, 600)
|
||||
check(E.eq(Math.allocate_whole(requests, limit), Legacy.allocate_whole(requests, limit)), "Allocation matches legacy with limits, ties and big integers")
|
||||
var whole_requests: Dictionary = {}
|
||||
for id in requests: whole_requests[id] = E.floor_value(E.maximum(0, requests[id]))
|
||||
check(E.eq(Math.allocate_whole(whole_requests, limit), Math.allocate_whole_with_total(whole_requests, limit, Math.sum(whole_requests))), "Known-total allocation matches ordinary allocation")
|
||||
var count: Variant = limit
|
||||
check(E.eq(Math.split(new_remainders, "population", count, weights), Legacy.split(old_remainders, "population", count, weights)), "Split matches legacy rounding")
|
||||
check(E.eq(new_remainders, old_remainders), "Signed remainder debt matches legacy")
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Civ = preload("res://world/owniverse/civilization_evolution.gd")
|
||||
const Fixture = preload("res://tests/fixtures/civilization_benchmark.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
func _initialize(): call_deferred("run")
|
||||
func check(value: bool, message: String):
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
|
||||
func reference_drift(target: Dictionary, pop: Dictionary, rows: Dictionary, diplomacy: bool, colony: bool) -> Dictionary:
|
||||
var weights: Dictionary = {}
|
||||
for other in Civ.ETHICS: weights[other] = 0
|
||||
for ethic in pop:
|
||||
var contribution: Variant = E.multiply(pop[ethic], E.of(rows[ethic].diplomacy_rate) if diplomacy else 1)
|
||||
if E.eq(contribution, 0): continue
|
||||
for other in Civ.ETHICS:
|
||||
var weight: int = 9 if colony else 1
|
||||
if other == ethic: weight = 960 if colony else 196
|
||||
elif other == Civ.OPPOSITE[ethic]: weight = 4 if colony else 0
|
||||
weights[other] = E.add(weights[other], E.multiply(contribution, weight))
|
||||
if E.le(Civ.total(weights), 0): return pop.duplicate()
|
||||
return Civ.split(target, "test", Civ.total(pop), weights)
|
||||
|
||||
func run():
|
||||
var rows: Dictionary = {}
|
||||
for ethic in Civ.ETHICS: rows[ethic] = {"diplomacy_rate": "0.125" if ethic == "m" else "1.75"}
|
||||
for count in [0, 1, 7, 999999999, E.of("1e40")]:
|
||||
for diplomacy in [false, true]:
|
||||
for colony in [false, true]:
|
||||
var actual: Dictionary = {}
|
||||
var reference: Dictionary = {}
|
||||
for round_index in 5:
|
||||
var pop: Dictionary = {"m": count, "l": round_index, "w": 9, "d": 0, "c": 137, "b": 2}
|
||||
var result: Dictionary = Civ.drift(actual, "test", pop, rows, diplomacy, colony)
|
||||
if diplomacy:
|
||||
check(E.eq(result, reference_drift(reference, pop, rows, diplomacy, colony)), "Diplomatic drift preserves allocation")
|
||||
var carries_valid: bool = true
|
||||
for value in actual.get("civilization_remainders", {}).values():
|
||||
carries_valid = carries_valid and E.ge(value, -1) and E.le(value, 1)
|
||||
check(carries_valid, "Diplomatic drift carries stay bounded")
|
||||
else:
|
||||
check(E.eq(Civ.total(result), Civ.total(pop)), "Integer flow drift conserves population")
|
||||
var valid_whole: bool = true
|
||||
for ethic in Civ.ETHICS:
|
||||
valid_whole = valid_whole and E.ge(result[ethic], 0) and E.eq(result[ethic], E.floor_value(result[ethic]))
|
||||
check(valid_whole, "Integer flow drift has nonnegative whole counts")
|
||||
var decimal_rows: Dictionary = {}
|
||||
for ethic in Civ.ETHICS: decimal_rows[ethic] = {"diplomacy_rate": "0.333333333333" if ethic == "m" else "0.017"}
|
||||
var decimal_target: Dictionary = {}
|
||||
for round_index in 20:
|
||||
var pop: Dictionary = {"m": E.of("1e40"), "l": round_index, "w": 17, "d": 4, "c": 91, "b": 2}
|
||||
var result: Dictionary = Civ.drift(decimal_target, "decimal", pop, decimal_rows, true)
|
||||
check(E.eq(Civ.total(result), Civ.total(pop)), "Decimal diplomatic drift preserves whole population")
|
||||
for ethic in result:
|
||||
check(E.ge(result[ethic], 0) and E.eq(result[ethic], E.floor_value(result[ethic])), "Decimal drift returns nonnegative whole counts")
|
||||
for speed in [1, 4, 6]:
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
||||
Fixture.seed(world, root.get_node("Global"), true, true)
|
||||
var rules: Dictionary = root.get_node("Global").CivilizationRules
|
||||
var id: String = rules.keys()[0]
|
||||
var modifiers: Dictionary = {"progress": 1, "degradation": 1, "war": 1, "colonization": 1}
|
||||
var original: Dictionary = Civ.coefficients(world, rules, id, modifiers).duplicate(true)
|
||||
modifiers.progress = 2
|
||||
var changed: Dictionary = Civ.coefficients(world, rules, id, modifiers)
|
||||
var row: Dictionary = rules[id].ethics.m
|
||||
var p: Variant = E.maximum(0, E.multiply(E.of(row.progress), 2))
|
||||
var d: Variant = E.maximum(0, E.of(row.degradation))
|
||||
check(E.eq(changed.m.pro, E.divide(p, E.maximum(1, E.add(p, d)))), "Coefficient cache follows modifiers")
|
||||
modifiers.progress = 1
|
||||
check(E.eq(Civ.coefficients(world, rules, id, modifiers), original), "Coefficient cache restores original rates")
|
||||
var local_rules: Dictionary = rules.duplicate(true)
|
||||
local_rules[id].ethics.m.progress = "0.375"
|
||||
changed = Civ.coefficients(world, local_rules, id, modifiers)
|
||||
check(E.eq(changed.m.pro, E.divide(E.of("0.375"), E.maximum(1, E.add(E.of("0.375"), d)))), "Coefficient cache follows rule edits")
|
||||
var target: Dictionary = {"ethics": {}}
|
||||
Civ.State.reconcile(world, target, rules, {id: 60})
|
||||
target.ethics[id].m = 100
|
||||
Civ.State.reconcile(world, target, rules, {id: 60})
|
||||
check(E.eq(Civ.total(target.ethics[id]), 60), "Reconcile cache detects changed ethics")
|
||||
Civ.State.reconcile(world, target, rules, {id: 120})
|
||||
check(E.eq(Civ.total(target.ethics[id]), 120), "Reconcile cache detects changed population")
|
||||
Civ.worship(world, target, rules)
|
||||
target.ethics[id].m = E.add(target.ethics[id].m, 5)
|
||||
var expected: Variant = 0
|
||||
for stage in rules:
|
||||
if rules[stage].worships:
|
||||
for ethic in target.ethics[stage]:
|
||||
expected = E.add(expected, E.multiply(target.ethics[stage][ethic], E.of(rules[stage].ethics[ethic].worship)))
|
||||
check(E.eq(Civ.worship(world, target, rules), expected), "Worship cache detects changed ethics")
|
||||
var worship_rules: Dictionary = rules.duplicate(true)
|
||||
for stage in worship_rules:
|
||||
if worship_rules[stage].worships:
|
||||
worship_rules[stage].ethics.m.worship = "0.375"
|
||||
expected = 0
|
||||
for stage in worship_rules:
|
||||
if worship_rules[stage].worships:
|
||||
for ethic in target.ethics[stage]:
|
||||
expected = E.add(expected, E.multiply(target.ethics[stage][ethic], E.of(worship_rules[stage].ethics[ethic].worship)))
|
||||
check(E.eq(Civ.worship(world, target, worship_rules), expected), "Worship cache detects changed rules")
|
||||
world.ledger = load("res://tests/fixtures/audit_rounding_ledger.gd").new(world)
|
||||
world.speed = speed
|
||||
for i in 3: world.evolute(1.0 if speed == 1 else 0.1)
|
||||
check(world.ledger.checks > 0 and world.ledger.failures == 0, "Selective and full rounding agree at speed %d" % speed)
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
print("Simulation optimizations: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcvertbh5uxys
|
||||
@@ -0,0 +1,66 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Q = preload("res://world/owniverse/quantity_math.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
func _initialize(): call_deferred("run")
|
||||
func check(value: bool, message: String):
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
static func reference_split(remainders: Dictionary, key: String, count: Variant, weights: Dictionary) -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
var sum_weights: Variant = Q.sum(weights)
|
||||
if (E.le(count, 0) or E.le(sum_weights, 0)): return result
|
||||
var ids: Array = weights.keys()
|
||||
var expected: Array = []
|
||||
var remainder_keys: Array = []
|
||||
var positive_sum: Variant = 0
|
||||
for id in ids:
|
||||
var remainder_key: String = key + ":" + str(id)
|
||||
remainder_keys.append(remainder_key)
|
||||
var value: Variant = E.add(E.mul_div(count, weights[id], sum_weights), remainders.get(remainder_key, 0))
|
||||
expected.append(value)
|
||||
positive_sum = E.add(positive_sum, E.maximum(0, value))
|
||||
var order: Array = []
|
||||
var rests: Array = []
|
||||
var assigned: Variant = 0
|
||||
for i in ids.size():
|
||||
var normalized: Variant = E.mul_div(E.maximum(0, expected[i]), count, positive_sum) if E.gt(positive_sum, 0) else 0
|
||||
result[ids[i]] = E.floor_value(normalized)
|
||||
assigned = E.add(assigned, result[ids[i]])
|
||||
rests.append(E.subtract(normalized, result[ids[i]]))
|
||||
order.append(i)
|
||||
order.sort_custom(func(a: int, b: int) -> bool: return E.gt(rests[a], rests[b]) if E.ne(rests[a], rests[b]) else str(ids[a]) < str(ids[b]))
|
||||
for i in order:
|
||||
if E.ge(assigned, count): break
|
||||
result[ids[i]] = E.add(result[ids[i]], 1)
|
||||
assigned = E.add(assigned, 1)
|
||||
for i in ids.size():
|
||||
remainders[remainder_keys[i]] = E.clamp_value(E.subtract(expected[i], result[ids[i]]), E.neg(1), 1)
|
||||
return result
|
||||
|
||||
func run():
|
||||
for count in [0, 1, 7, E.of("1.375"), 1000000, E.of("1e40"), E.of("1e80")]:
|
||||
for weights in [{}, {"a": 0, "b": 0}, {"a": 0, "b": 7}, {"b": 0, "a": 7}, {"a": 1, "b": 1, "c": 1}, {"a": E.of("0.017"), "b": E.of("0.333333333333"), "c": 0}, {"a": E.of("1e40"), "b": 17, "c": 1}]:
|
||||
for carry in [0, E.of("0.25"), E.of("-0.75")]:
|
||||
var actual: Dictionary = {"case:a": carry}
|
||||
var expected: Dictionary = actual.duplicate(true)
|
||||
for iteration in 5:
|
||||
var reference: Dictionary = reference_split(expected, "case", count, weights)
|
||||
var result: Dictionary = Q.split(actual, "case", count, weights)
|
||||
check(E.eq(result, reference), "Split preserves allocation")
|
||||
check(E.eq(actual, expected), "Split preserves rounding carries")
|
||||
for a in [0, 7, E.of("1e40"), E.of("-1e40"), E.of("1.375")]:
|
||||
var values: Array = [0, 1, -7, E.of("0.017"), E.of("1e30")]
|
||||
for divisor in [1, 3, E.of("0.017"), E.of("1e40")]:
|
||||
var results: Array = E.mul_div_many(a, values, divisor)
|
||||
for i in values.size(): check(E.eq(results[i], E.mul_div(a, values[i], divisor)), "Grouped ratios preserve scalar results")
|
||||
for count in [1, 7, 1000000000, E.of("1e40"), E.of("1e80")]:
|
||||
for factor in [200, 1000]:
|
||||
var weights: Array = [0, 17, E.of("1e40"), E.multiply(count, factor)]
|
||||
var results: Array = E.population_quotas(count, weights, factor)
|
||||
for i in weights.size(): check(E.eq(results[i], E.mul_div(count, weights[i], E.multiply(count, factor))), "Cancelled population preserves exact scalar quotas")
|
||||
print("Split optimizations: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b4e1nd22q1705
|
||||
@@ -0,0 +1,14 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
func _initialize():
|
||||
# SceneTree initialization runs before Global._ready, like main scene construction.
|
||||
var global = root.get_node("Global")
|
||||
assert(not global.is_node_ready(), "Test must cover pre-ready initialization")
|
||||
assert(global.ExtraParams.has("E"), "Energy table exists before Global._ready")
|
||||
var scene = load("res://world/world.tscn").instantiate()
|
||||
var stage = scene.get_node("Stage")
|
||||
assert(E.eq(stage.owniverse.state.energy, global.ExtraParams.E), "Stage starts with configured energy")
|
||||
stage.owniverse.free()
|
||||
scene.free()
|
||||
print("Startup params: 3 checks, 0 failures")
|
||||
quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://5cbgaqn5s1yw
|
||||
@@ -65,6 +65,28 @@ func run() -> void:
|
||||
check(stage.effects_player.stream == stage.effects_player._effects.window_open, "Opening a window plays its effect")
|
||||
stage._on_topmenu_button_pressed("World")
|
||||
check(stage.effects_player.stream == stage.effects_player._effects.window_close, "Closing a window plays its effect")
|
||||
stage.indicator_elapsed = 1.0
|
||||
stage.active_actions_elapsed = 1.0
|
||||
stage.tick_ui(0.0)
|
||||
var refreshes: int = stage.actions_refreshes
|
||||
stage.date_time.text = "unchanged"
|
||||
stage.active_actions_elapsed = 1.0
|
||||
stage.tick_ui(0.0)
|
||||
check(stage.actions_refreshes == refreshes, "Idle UI does not recalculate production limits")
|
||||
check(stage.date_time.text == "unchanged", "Idle UI does not reformat the clock")
|
||||
stage.owniverse.state.amount.H = 3456
|
||||
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog)
|
||||
stage.tick_ui(0.0)
|
||||
check(hydrogen.description.text == "3 456", "A manual publication updates idle UI")
|
||||
check(stage.actions_refreshes == refreshes + 1, "A publication invalidates production limits")
|
||||
stage.active_actions_dirty = true
|
||||
stage.tick_ui(0.0)
|
||||
check(stage.actions_refreshes == refreshes + 2, "Selection invalidation refreshes limits immediately")
|
||||
var old_pointer: float = stage.world_indicators.pointers.Agg.position.x
|
||||
stage.owniverse.environment.modify_world_parameter("Agg", 1)
|
||||
stage.indicator_elapsed = 1.0
|
||||
stage.tick_ui(0.0)
|
||||
check(stage.world_indicators.pointers.Agg.position.x != old_pointer, "World parameter actions invalidate indicators")
|
||||
for entity in stage.owniverse.catalog.entities.values(): entity.free()
|
||||
stage.owniverse.free()
|
||||
scene.free()
|
||||
|
||||
@@ -7,6 +7,8 @@ const SILENT_VOLUME_DB := -80.0
|
||||
@onready var music_button: TextureButton = $Media/Music
|
||||
@onready var sound_volume: HSlider = $Volume/SoundVolume
|
||||
@onready var music_volume: HSlider = $Volume/MusicVolume
|
||||
@onready var english_button: TextureButton = $Language/English
|
||||
@onready var russian_button: TextureButton = $Language/Russian
|
||||
@onready var effects_player: OwniverseEffectsPlayer = $"../../FXPlayer"
|
||||
@onready var music_player: OwniverseMusicPlayer = $"../../MusicPlayer"
|
||||
|
||||
@@ -17,6 +19,7 @@ func _ready() -> void:
|
||||
music_button.set_pressed_no_signal(bool(Global.Settings.music_enabled))
|
||||
sound_volume.set_value_no_signal(float(Global.Settings.sound_volume) * sound_volume.max_value)
|
||||
music_volume.set_value_no_signal(float(Global.Settings.music_volume) * music_volume.max_value)
|
||||
_update_locale_buttons()
|
||||
_apply_sound_settings()
|
||||
_apply_music_settings()
|
||||
|
||||
@@ -65,3 +68,14 @@ func _apply_bus_volume(bus_name: String, value: float) -> void:
|
||||
var normalized: float = clampf(value, 0.0, 1.0)
|
||||
var volume_db: float = SILENT_VOLUME_DB if is_zero_approx(normalized) else lerpf(MIN_VOLUME_DB, 0.0, normalized)
|
||||
AudioServer.set_bus_volume_db(bus_index, volume_db)
|
||||
|
||||
|
||||
func _on_change_locale_pressed(locale: String) -> void:
|
||||
Global.set_locale(locale)
|
||||
_update_locale_buttons()
|
||||
SaveManager.save_settings()
|
||||
|
||||
|
||||
func _update_locale_buttons() -> void:
|
||||
english_button.set_pressed_no_signal(Global.Locale == "en")
|
||||
russian_button.set_pressed_no_signal(Global.Locale == "ru")
|
||||
|
||||
@@ -203,20 +203,12 @@ func auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Dictiona
|
||||
return (E.multiply(E.floor_value(E.divide(capacity, step)), step) if (E.finite(capacity) and E.finite(step)) else 0)
|
||||
|
||||
|
||||
func produce_newborn_lifetime(target: Dictionary, id: String, count: Variant, born_from: Variant, born_to: Variant, to_year: Variant, before_batches: Array, remaining_object_years: Variant = NAN) -> void:
|
||||
func produce_newborn_lifetime(target: Dictionary, id: String, count: Variant, born_from: Variant, born_to: Variant, to_year: Variant, newborn_past_years: Variant = 0, remaining_object_years: Variant = NAN) -> void:
|
||||
var products: Dictionary = yearly_products(world.catalog.entities[id])
|
||||
if (products.is_empty() or E.le(count, 0)):
|
||||
return
|
||||
var object_years: Variant = (remaining_object_years if E.finite(remaining_object_years) else E.multiply(count, E.maximum(0, E.subtract(to_year, E.multiply((E.add(born_from, born_to)), E.of("0.5"))))))
|
||||
var previous_amounts: Dictionary = {}
|
||||
for old : Dictionary in before_batches:
|
||||
var start: Variant = old.get("created_from", E.neg(INF))
|
||||
if (not previous_amounts.has(str(start))):
|
||||
previous_amounts[str(start)] = old.amount
|
||||
for batch : Dictionary in target.batches.get(id, []):
|
||||
var added: Variant = E.subtract(batch.amount, previous_amounts.get(str(batch.get("created_from", INF)), 0))
|
||||
object_years = E.subtract(object_years, E.multiply(E.maximum(0, added), world.batches.expired_years(to_year, batch.start_year, batch.end_year)))
|
||||
object_years = E.maximum(0, object_years)
|
||||
object_years = E.maximum(0, E.subtract(object_years, newborn_past_years))
|
||||
for resource in products:
|
||||
var coefficient: Variant = E.multiply(products[resource], WorldParameters.production(target, world.catalog.entities[id]))
|
||||
var output: Variant = E.multiply(object_years, coefficient)
|
||||
|
||||
@@ -7,6 +7,8 @@ const State = preload("res://world/owniverse/civilization_state.gd")
|
||||
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
|
||||
const ETHICS = State.ETHICS
|
||||
const OPPOSITE = {"m": "l", "l": "m", "w": "d", "d": "w", "c": "b", "b": "c"}
|
||||
const DIPLOMACY_SCALE: int = 1000000000
|
||||
static var _diplomacy_rates: Dictionary = {}
|
||||
|
||||
static func total(pop: Dictionary) -> Variant:
|
||||
return QuantityMath.sum(pop)
|
||||
@@ -14,8 +16,8 @@ static func total(pop: Dictionary) -> Variant:
|
||||
static func add(to: Dictionary, from: Dictionary) -> void:
|
||||
for key in from: to[key] = E.add(to.get(key, 0), from[key])
|
||||
|
||||
static func take(world, pop: Dictionary, count: Variant) -> Dictionary:
|
||||
var taken: Dictionary = QuantityMath.allocate_whole(pop, count)
|
||||
static func take(world, pop: Dictionary, count: Variant, population_total: Variant = null) -> Dictionary:
|
||||
var taken: Dictionary = QuantityMath.allocate_whole_with_total(pop, count, population_total) if population_total != null else QuantityMath.allocate_whole(pop, count)
|
||||
for key in taken: pop[key] = E.subtract(pop[key], taken[key])
|
||||
return taken
|
||||
|
||||
@@ -30,18 +32,124 @@ static func split(target: Dictionary, key: String, count: Variant, weights: Dict
|
||||
if (not target.has("civilization_remainders")): target.civilization_remainders = {}
|
||||
return QuantityMath.split(target.civilization_remainders, key, count, weights)
|
||||
|
||||
static func drift(target: Dictionary, key: String, pop: Dictionary, rows: Dictionary, diplomacy: bool = false, colony: bool = false) -> Dictionary:
|
||||
static func drift(target: Dictionary, key: String, pop: Dictionary, rows: Dictionary, diplomacy: bool = false, colony: bool = false, population: Variant = null) -> Dictionary:
|
||||
if population == null: population = total(pop)
|
||||
if E.le(population, 0): return pop.duplicate()
|
||||
if not diplomacy and E.eq(population, E.floor_value(population)):
|
||||
return direct_drift(target, key, pop, colony, population)
|
||||
var weights: Array = []
|
||||
if not diplomacy:
|
||||
# Integer populations: exact algebraic reduction of the 6 x 6 matrix.
|
||||
var common: Variant = E.multiply(population, 9) if colony else population
|
||||
for ethic in ETHICS:
|
||||
weights.append(E.subtract(E.add(common, E.multiply(pop.get(ethic, 0), 951 if colony else 195)), E.multiply(pop.get(OPPOSITE[ethic], 0), 5 if colony else 1)))
|
||||
else:
|
||||
# Algebraic reduction of the diplomatic 6 x 6 matrix.
|
||||
var rates: Array = diplomacy_rates(rows)
|
||||
var contributions: Array = [0, 0, 0, 0, 0, 0]
|
||||
var combined: Variant = 0
|
||||
for ethic in pop:
|
||||
var index: int = ETHICS.find(ethic)
|
||||
var contribution: Variant = E.multiply(pop[ethic], rates[index])
|
||||
contributions[index] = contribution
|
||||
combined = E.add(combined, contribution)
|
||||
var common: Variant = E.multiply(combined, 9) if colony else combined
|
||||
for ethic in ETHICS:
|
||||
weights.append(E.maximum(0, E.subtract(E.add(common, E.multiply(contributions[ETHICS.find(ethic)], 951 if colony else 195)), E.multiply(contributions[ETHICS.find(OPPOSITE[ethic])], 5 if colony else 1))))
|
||||
if E.le(QuantityMath.sum_array(weights), 0): return pop.duplicate()
|
||||
if not target.has("civilization_remainders"): target.civilization_remainders = {}
|
||||
return QuantityMath.split_arrays(target.civilization_remainders, key, population, ETHICS, weights, 0)
|
||||
|
||||
static func diplomacy_rates(rows: Dictionary) -> Array:
|
||||
var signature: String = ""
|
||||
for ethic in ETHICS: signature += str(rows[ethic].diplomacy_rate) + ";"
|
||||
if _diplomacy_rates.has(signature): return _diplomacy_rates[signature]
|
||||
var rates: Array = []
|
||||
for ethic in ETHICS:
|
||||
var exact_rate: Variant = E.of(rows[ethic].diplomacy_rate)
|
||||
rates.append(E.integer(E.round_value(E.multiply(exact_rate, DIPLOMACY_SCALE))))
|
||||
if _diplomacy_rates.size() >= 256: _diplomacy_rates.clear()
|
||||
rates.make_read_only()
|
||||
_diplomacy_rates[signature] = rates
|
||||
return rates
|
||||
|
||||
static func direct_drift(target: Dictionary, key: String, pop: Dictionary, colony: bool, population: Variant) -> Dictionary:
|
||||
if not target.has("civilization_remainders"): target.civilization_remainders = {}
|
||||
var carries: Dictionary = target.civilization_remainders
|
||||
var result: Dictionary = {}
|
||||
for ethic in ETHICS: result[ethic] = 0
|
||||
for ethic in ETHICS:
|
||||
if E.ne(pop.get(ethic, 0), E.floor_value(pop.get(ethic, 0))): return fractional_direct_fallback(target, key, pop, colony, population)
|
||||
var divisor: int = 25 if colony else 50
|
||||
for source in ETHICS:
|
||||
var count: Variant = pop.get(source, 0)
|
||||
if E.le(count, 0): continue
|
||||
var whole: Variant = E.floor_value(E.divide(count, divisor))
|
||||
var count_remainder: int = E.integer(E.subtract(count, E.multiply(whole, divisor)))
|
||||
var remainder_key: String = key + ":flow:" + source
|
||||
var pending: int = count_remainder + int(carries.get(remainder_key, 0))
|
||||
var outgoing: Variant = E.add(whole, pending / divisor)
|
||||
carries[remainder_key] = pending % divisor
|
||||
result[source] = E.add(result[source], E.subtract(count, outgoing))
|
||||
if E.le(outgoing, 0): continue
|
||||
if not colony:
|
||||
var neighbor_base: Variant = E.floor_value(E.divide(outgoing, 4))
|
||||
var extras: int = E.integer(E.subtract(outgoing, E.multiply(neighbor_base, 4)))
|
||||
var neighbors: Array = []
|
||||
for destination in ETHICS:
|
||||
if destination != source and destination != OPPOSITE[source]: neighbors.append(destination)
|
||||
var cursor_key: String = key + ":flow_cursor:" + source
|
||||
var cursor: int = int(carries.get(cursor_key, 0))
|
||||
for neighbor_index in 4:
|
||||
var destination: String = neighbors[neighbor_index]
|
||||
var add: Variant = E.add(neighbor_base, 1 if ((neighbor_index - cursor + 4) % 4) < extras else 0)
|
||||
result[destination] = E.add(result[destination], add)
|
||||
carries[cursor_key] = (cursor + extras) % 4
|
||||
else:
|
||||
distribute_colonial_flow(result, carries, key, source, outgoing)
|
||||
assert(E.eq(total(result), population), "Direct ethic drift must conserve population")
|
||||
return result
|
||||
|
||||
static func distribute_colonial_flow(result: Dictionary, carries: Dictionary, key: String, source: String, outgoing: Variant) -> void:
|
||||
# Colonial flow is 9:9:9:9:4 among the other ethics (total weight 40).
|
||||
var base: Variant = E.floor_value(E.divide(outgoing, 40))
|
||||
var tail: int = E.integer(E.subtract(outgoing, E.multiply(base, 40)))
|
||||
var destinations: Array = []
|
||||
var weights: Array = []
|
||||
for destination in ETHICS:
|
||||
if destination == source: continue
|
||||
destinations.append(destination)
|
||||
weights.append(4 if destination == OPPOSITE[source] else 9)
|
||||
var assigned: Variant = 0
|
||||
var rests: Array = []
|
||||
var quotas: Array = []
|
||||
for i in destinations.size():
|
||||
var carry_key: String = key + ":col_flow:" + source + ":" + destinations[i]
|
||||
var numerator: int = tail * weights[i] + int(carries.get(carry_key, 0))
|
||||
var extra: int = floori(float(numerator) / 40.0)
|
||||
var rest: int = numerator - extra * 40
|
||||
quotas.append(E.add(E.multiply(base, weights[i]), extra))
|
||||
rests.append(rest)
|
||||
assigned = E.add(assigned, quotas[i])
|
||||
carries[carry_key] = rest
|
||||
var missing: int = E.integer(E.subtract(outgoing, assigned))
|
||||
var order: Array = [0, 1, 2, 3, 4]
|
||||
order.sort_custom(func(a: int, b: int) -> bool: return rests[a] > rests[b] if rests[a] != rests[b] else destinations[a] < destinations[b])
|
||||
for i in order:
|
||||
if missing <= 0: break
|
||||
quotas[i] = E.add(quotas[i], 1)
|
||||
var carry_key: String = key + ":col_flow:" + source + ":" + destinations[i]
|
||||
carries[carry_key] = int(carries[carry_key]) - 40
|
||||
missing -= 1
|
||||
for i in destinations.size(): result[destinations[i]] = E.add(result[destinations[i]], quotas[i])
|
||||
|
||||
static func fractional_direct_fallback(target: Dictionary, key: String, pop: Dictionary, colony: bool, population: Variant) -> Dictionary:
|
||||
var weights: Dictionary = {}
|
||||
for ethic in pop:
|
||||
var influence: Variant = (E.of(rows[ethic].diplomacy_rate) if diplomacy else 1)
|
||||
var contribution: Variant = E.multiply(pop[ethic], influence)
|
||||
for other in ETHICS:
|
||||
var weight: Variant = (9 if colony else 1)
|
||||
if E.eq(other, ethic): weight = (960 if colony else 196)
|
||||
elif E.eq(other, OPPOSITE[ethic]): weight = (4 if colony else 0)
|
||||
weights[other] = E.add(weights.get(other, 0), E.multiply(contribution, weight))
|
||||
if E.le(total(weights), 0): return pop.duplicate()
|
||||
return split(target, key, total(pop), weights)
|
||||
for ethic in ETHICS:
|
||||
var common: Variant = E.multiply(population, 9) if colony else population
|
||||
weights[ethic] = E.subtract(E.add(common, E.multiply(pop.get(ethic, 0), 951 if colony else 195)), E.multiply(pop.get(OPPOSITE[ethic], 0), 5 if colony else 1))
|
||||
if not target.has("civilization_remainders"): target.civilization_remainders = {}
|
||||
return QuantityMath.split(target.civilization_remainders, key, population, weights)
|
||||
|
||||
static func emit(plan: Dictionary, destination: String, pop: Dictionary) -> void:
|
||||
if (not plan.outputs.has(destination)): plan.outputs[destination] = {}
|
||||
@@ -50,37 +158,85 @@ static func emit(plan: Dictionary, destination: String, pop: Dictionary) -> void
|
||||
static func fall(target: Dictionary, plan: Dictionary, rules: Dictionary, pop: Dictionary) -> void:
|
||||
for ethic in pop:
|
||||
if E.le(pop[ethic], 0): continue
|
||||
var weights: Dictionary = {}
|
||||
for term in str(rules[plan.id].ethics[ethic].fall).split("^", false):
|
||||
var pair: PackedStringArray = term.split(":")
|
||||
weights[pair[0]] = E.of(pair[1])
|
||||
if weights.is_empty(): weights[rules[plan.id].previous] = 1
|
||||
var weights: Dictionary = QuantityMath.rule_weights(str(rules[plan.id].ethics[ethic].fall))
|
||||
if weights.is_empty(): weights = {rules[plan.id].previous: 1}
|
||||
var shares: Dictionary = split(target, E.add(E.add(E.add("fall:", plan.id), ":"), ethic), pop[ethic], weights)
|
||||
for stage in shares: emit(plan, stage, {ethic: shares[stage]})
|
||||
|
||||
static func progress(world, target: Dictionary, rules: Dictionary, budget: Dictionary, plan: Dictionary, pop: Dictionary) -> Dictionary:
|
||||
static func progress(world, target: Dictionary, rules: Dictionary, budget: Dictionary, plan: Dictionary, pop: Dictionary, population_total: Variant = null) -> Dictionary:
|
||||
var successor: String = rules[plan.id].next
|
||||
var admitted: Variant = State.admit_transition(budget, rules, plan.id, successor, total(pop))
|
||||
var accepted: Dictionary = take(world, pop, admitted)
|
||||
if population_total == null: population_total = total(pop)
|
||||
var admitted: Variant = State.admit_transition(budget, rules, plan.id, successor, population_total)
|
||||
var accepted: Dictionary = take(world, pop, admitted, population_total)
|
||||
emit(plan, successor, accepted)
|
||||
return pop
|
||||
|
||||
static func new_plan(id: String, year: Variant) -> Dictionary:
|
||||
return {"id": id, "from": year, "to": E.neg(INF), "amount": 0, "outputs": {}}
|
||||
|
||||
static func consume_untriggered(world, target: Dictionary, plan: Dictionary, pop: Dictionary, count: Variant, year: Variant) -> Dictionary:
|
||||
var old_total: Variant = total(pop)
|
||||
var removed: Dictionary = take(world, pop, count)
|
||||
var actual: Variant = total(removed)
|
||||
static func consume_untriggered(world, target: Dictionary, plan: Dictionary, pop: Dictionary, count: Variant, year: Variant, population_total: Variant = null) -> Dictionary:
|
||||
var old_total: Variant = total(pop) if population_total == null else population_total
|
||||
var actual: Variant = E.floor_value(E.minimum(E.maximum(0, count), old_total))
|
||||
var removed: Dictionary = take(world, pop, actual, old_total)
|
||||
if E.gt(actual, 0):
|
||||
world.batches.scale_batches(target, plan.id, E.divide((E.subtract(old_total, actual)), old_total))
|
||||
plan.amount = E.add(plan.amount, actual)
|
||||
plan.to = E.maximum(plan.to, year)
|
||||
return removed
|
||||
|
||||
static func prepare(world, target: Dictionary, expired: Dictionary, year: Variant, budget: Dictionary, rules: Dictionary, physical: Dictionary = {}, budget_timer: RefCounted = null) -> Dictionary:
|
||||
# Cache products per ethic, then sum them in the original stage/ethic order.
|
||||
# Keeping the summation order preserves float fractional rounding exactly.
|
||||
static func worship(world, target: Dictionary, rules: Dictionary) -> Variant:
|
||||
var result: Variant = 0
|
||||
for id in rules:
|
||||
if not rules[id].worships: continue
|
||||
var pop: Dictionary = target.ethics[id]
|
||||
var coefficients: Dictionary = {}
|
||||
for ethic in pop: coefficients[ethic] = rules[id].ethics[ethic].worship
|
||||
var cached: Dictionary = world.ledger.worship_products.get(id, {})
|
||||
if cached.is_empty() or not E.eq(cached.population, pop) or not E.eq(cached.coefficients, coefficients):
|
||||
var products: Dictionary = {}
|
||||
for ethic in pop: products[ethic] = E.multiply(pop[ethic], E.of(coefficients[ethic]))
|
||||
cached = {"population": pop.duplicate(), "coefficients": coefficients, "products": products}
|
||||
world.ledger.worship_products[id] = cached
|
||||
for ethic in pop: result = E.add(result, cached.products[ethic])
|
||||
return result
|
||||
|
||||
static func coefficients(world, rules: Dictionary, id: String, modifiers: Dictionary) -> Dictionary:
|
||||
if not E.eq(world.ledger.civilization_modifiers, modifiers):
|
||||
world.ledger.civilization_modifiers = modifiers.duplicate()
|
||||
world.ledger.civilization_coefficients.clear()
|
||||
var cached: Dictionary = world.ledger.civilization_coefficients.get(id, {})
|
||||
var signature: String = ""
|
||||
for ethic in ETHICS:
|
||||
var row: Dictionary = rules[id].ethics[ethic]
|
||||
signature += ethic + ":" + str(row.progress) + "," + str(row.degradation) + "," + str(row.war) + "," + str(row.colonization) + ";"
|
||||
if not cached.is_empty() and cached.signature == signature: return cached.rates
|
||||
var rates: Dictionary = {}
|
||||
for ethic in rules[id].ethics:
|
||||
var row: Dictionary = rules[id].ethics[ethic]
|
||||
var p: Variant = E.maximum(0, E.multiply(E.of(row.progress), modifiers.progress))
|
||||
var d: Variant = E.maximum(0, E.multiply(E.of(row.degradation), modifiers.degradation))
|
||||
var scale: Variant = E.maximum(1, E.add(p, d))
|
||||
rates[ethic] = {"pro": E.divide(p, scale), "deg": E.divide(d, scale),
|
||||
"war": E.minimum(1, E.multiply(E.of(row.war), modifiers.war)),
|
||||
"col": E.minimum(1, E.multiply(E.of(row.colonization), modifiers.colonization))}
|
||||
world.ledger.civilization_coefficients[id] = {"signature": signature, "rates": rates}
|
||||
return rates
|
||||
|
||||
static func prepare(world, target: Dictionary, expired: Dictionary, year: Variant, budget: Dictionary, rules: Dictionary, physical: Dictionary = {}, budget_timer: RefCounted = null, expired_totals: Dictionary = {}) -> Dictionary:
|
||||
var profile_started: int = world.profile_start()
|
||||
State.reconcile(world, target, rules, physical)
|
||||
var ids: Array = []
|
||||
for id in expired:
|
||||
if rules.has(id) and not expired[id].is_empty(): ids.append(id)
|
||||
if ids.is_empty():
|
||||
world.profile_end("civilization_prepare", profile_started)
|
||||
return {"active": false}
|
||||
var reconcile_ids: Array = []
|
||||
for id in ids:
|
||||
reconcile_ids.append(id)
|
||||
if rules[id].fights and not reconcile_ids.has(rules[id].previous): reconcile_ids.append(rules[id].previous)
|
||||
State.reconcile(world, target, rules, physical, reconcile_ids)
|
||||
var remaining: Dictionary = target.ethics.duplicate()
|
||||
var copied: Dictionary = {}
|
||||
var plans: Dictionary = {}
|
||||
@@ -89,15 +245,15 @@ static func prepare(world, target: Dictionary, expired: Dictionary, year: Varian
|
||||
var modifiers: Dictionary = WorldParameters.life_modifiers(target)
|
||||
var order: Variant = WorldParameters.value(target, "Order")
|
||||
if (not target.has("civilization_stats")): target.civilization_stats = {}
|
||||
var ids: Array = expired.keys()
|
||||
ids.sort()
|
||||
for id in ids:
|
||||
if budget_timer != null and not await budget_timer.pause(): return {}
|
||||
if ((not rules.has(id)) or expired[id].is_empty()): continue
|
||||
var plan: Dictionary = new_plan(id, year)
|
||||
plans[id] = plan
|
||||
plan.amount = expired_totals.get(id, 0)
|
||||
for part : Dictionary in expired[id]:
|
||||
plan.amount = E.add(plan.amount, part.amount)
|
||||
if not expired_totals.has(id): plan.amount = E.add(plan.amount, part.amount)
|
||||
plan.from = E.minimum(plan.from, part.from)
|
||||
plan.to = E.maximum(plan.to, part.to)
|
||||
remaining[id] = remaining[id].duplicate()
|
||||
@@ -107,16 +263,15 @@ static func prepare(world, target: Dictionary, expired: Dictionary, year: Varian
|
||||
var failures: Dictionary = {}
|
||||
var warriors: Dictionary = {}
|
||||
var settlers: Dictionary = {}
|
||||
var stage_rates: Dictionary = coefficients(world, rules, id, modifiers)
|
||||
for ethic in triggered:
|
||||
var row: Dictionary = rules[id].ethics[ethic]
|
||||
var p: Variant = E.maximum(0, E.multiply(E.of(row.progress), modifiers.progress))
|
||||
var d: Variant = E.maximum(0, E.multiply(E.of(row.degradation), modifiers.degradation))
|
||||
var scale: Variant = E.maximum(1, E.add(p, d))
|
||||
var pro: Variant = rate(target, E.add(E.add(E.add(id, ":"), ethic), ":pro"), triggered[ethic], E.divide(p, scale))
|
||||
var deg: Variant = rate(target, E.add(E.add(E.add(id, ":"), ethic), ":deg"), triggered[ethic], E.divide(d, scale))
|
||||
var probability: Dictionary = stage_rates[ethic]
|
||||
var prefix: String = id + ":" + ethic
|
||||
var pro: Variant = rate(target, prefix + ":pro", triggered[ethic], probability.pro)
|
||||
var deg: Variant = rate(target, prefix + ":deg", triggered[ethic], probability.deg)
|
||||
var bounded: Dictionary = QuantityMath.allocate_whole({"pro": pro, "deg": deg}, triggered[ethic])
|
||||
if rules[id].fights: warriors[ethic] = rate(target, E.add(E.add(E.add(id, ":"), ethic), ":war"), triggered[ethic], E.minimum(1, E.multiply(E.of(row.war), modifiers.war)))
|
||||
if rules[id].colonizes: settlers[ethic] = rate(target, E.add(E.add(E.add(id, ":"), ethic), ":col"), triggered[ethic], E.minimum(1, E.multiply(E.of(row.colonization), modifiers.colonization)))
|
||||
if rules[id].fights: warriors[ethic] = rate(target, prefix + ":war", triggered[ethic], probability.war)
|
||||
if rules[id].colonizes: settlers[ethic] = rate(target, prefix + ":col", triggered[ethic], probability.col)
|
||||
successes[ethic] = bounded.pro
|
||||
failures[ethic] = bounded.deg
|
||||
triggered[ethic] = E.subtract(triggered[ethic], E.add(bounded.pro, bounded.deg))
|
||||
@@ -127,22 +282,36 @@ static func prepare(world, target: Dictionary, expired: Dictionary, year: Varian
|
||||
if rules[id].fights:
|
||||
var profile_wars: int = world.profile_start()
|
||||
for ethic in warriors: warriors[ethic] = E.minimum(warriors[ethic], triggered[ethic])
|
||||
var wars: Variant = E.minimum(total(warriors), E.floor_value(E.divide((E.add(total(triggered), total(remaining[id]))), 2)))
|
||||
warriors = QuantityMath.allocate_whole(warriors, wars)
|
||||
var warriors_total: Variant = total(warriors)
|
||||
var triggered_total: Variant = total(triggered)
|
||||
var remaining_total: Variant = total(remaining[id])
|
||||
var wars: Variant = E.minimum(warriors_total, E.floor_value(E.divide(E.add(triggered_total, remaining_total), 2)))
|
||||
warriors = QuantityMath.allocate_whole_with_total(warriors, wars, warriors_total)
|
||||
for ethic in warriors: triggered[ethic] = E.subtract(triggered[ethic], warriors[ethic])
|
||||
triggered_total = E.subtract(triggered_total, wars)
|
||||
var outcomes: Dictionary = split(target, E.add(id, ":wars"), wars, {"deg": 1, "pro25": 2, "pro50": 3, "pro75": 3})
|
||||
var win_expected: Variant = E.add(E.add(E.multiply(outcomes.get("pro25", 0), E.of("0.25")), E.multiply(outcomes.get("pro50", 0), E.of("0.5"))), E.multiply(outcomes.get("pro75", 0), E.of("0.75")))
|
||||
var loss_expected: Variant = E.add(E.add(E.add(E.multiply(outcomes.get("deg", 0), 2), outcomes.get("pro25", 0)), E.multiply(outcomes.get("pro50", 0), E.of("1.5"))), E.multiply(outcomes.get("pro75", 0), 2))
|
||||
var winners: Dictionary = take(world, warriors, rate(target, E.add(id, ":wins"), win_expected, 1))
|
||||
var attackers_lost: Dictionary = take(world, warriors, rate(target, E.add(id, ":attackers_lost"), E.divide(outcomes.get("deg", 0), order), 1))
|
||||
add(triggered, progress(world, target, rules, budget, plan, winners))
|
||||
var wins_requested: Variant = rate(target, E.add(id, ":wins"), win_expected, 1)
|
||||
var winners: Dictionary = take(world, warriors, wins_requested, wars)
|
||||
var winner_total: Variant = E.floor_value(E.minimum(E.maximum(0, wins_requested), wars))
|
||||
warriors_total = E.subtract(wars, winner_total)
|
||||
var losses_requested: Variant = rate(target, E.add(id, ":attackers_lost"), E.divide(outcomes.get("deg", 0), order), 1)
|
||||
var attackers_lost: Dictionary = take(world, warriors, losses_requested, warriors_total)
|
||||
var attackers_lost_total: Variant = E.floor_value(E.minimum(E.maximum(0, losses_requested), warriors_total))
|
||||
warriors_total = E.subtract(warriors_total, attackers_lost_total)
|
||||
add(triggered, progress(world, target, rules, budget, plan, winners, winner_total))
|
||||
triggered_total = total(triggered)
|
||||
fall(target, plan, rules, attackers_lost)
|
||||
var losses: Variant = E.minimum(rate(target, E.add(id, ":loss"), E.divide(loss_expected, order), 1), E.add(total(triggered), total(remaining[id])))
|
||||
var sides: Dictionary = QuantityMath.allocate_whole({"due": total(triggered), "later": total(remaining[id])}, losses)
|
||||
var victims: Dictionary = take(world, triggered, sides.due)
|
||||
add(victims, consume_untriggered(world, target, plan, remaining[id], sides.later, year))
|
||||
remaining_total = total(remaining[id])
|
||||
var losses: Variant = E.minimum(rate(target, E.add(id, ":loss"), E.divide(loss_expected, order), 1), E.add(triggered_total, remaining_total))
|
||||
var sides: Dictionary = QuantityMath.allocate_whole_with_total({"due": triggered_total, "later": remaining_total}, losses, E.add(triggered_total, remaining_total))
|
||||
var victims: Dictionary = take(world, triggered, sides.due, triggered_total)
|
||||
triggered_total = E.subtract(triggered_total, sides.due)
|
||||
add(victims, consume_untriggered(world, target, plan, remaining[id], sides.later, year, remaining_total))
|
||||
fall(target, plan, rules, victims)
|
||||
add(triggered, warriors)
|
||||
triggered_total = E.add(triggered_total, warriors_total)
|
||||
var damage: Variant = rate(target, E.add(id, ":collateral"), E.divide((E.add(E.multiply(outcomes.get("pro50", 0), 10), E.multiply(outcomes.get("pro75", 0), 30))), order), 1)
|
||||
collateral[rules[id].previous] = E.add(collateral.get(rules[id].previous, 0), damage)
|
||||
target.civilization_stats.wars = E.add(target.civilization_stats.get("wars", 0), wars)
|
||||
@@ -158,10 +327,12 @@ static func prepare(world, target: Dictionary, expired: Dictionary, year: Varian
|
||||
if (not plans.has(id)): plans[id] = new_plan(id, year)
|
||||
var plan: Dictionary = plans[id]
|
||||
var retry: Dictionary = plan.outputs.get(id, {})
|
||||
var amount: Variant = E.minimum(collateral[id], E.add(total(retry), total(remaining[id])))
|
||||
var sides: Dictionary = QuantityMath.allocate_whole({"due": total(retry), "later": total(remaining[id])}, amount)
|
||||
var victims: Dictionary = take(world, retry, sides.due)
|
||||
add(victims, consume_untriggered(world, target, plan, remaining[id], sides.later, year))
|
||||
var retry_total: Variant = total(retry)
|
||||
var remaining_total: Variant = total(remaining[id])
|
||||
var amount: Variant = E.minimum(collateral[id], E.add(retry_total, remaining_total))
|
||||
var sides: Dictionary = QuantityMath.allocate_whole_with_total({"due": retry_total, "later": remaining_total}, amount, E.add(retry_total, remaining_total))
|
||||
var victims: Dictionary = take(world, retry, sides.due, retry_total)
|
||||
add(victims, consume_untriggered(world, target, plan, remaining[id], sides.later, year, remaining_total))
|
||||
fall(target, plan, rules, victims)
|
||||
target.civilization_stats.collateral = E.add(target.civilization_stats.get("collateral", 0), total(victims))
|
||||
var incoming: Dictionary = {}
|
||||
@@ -172,22 +343,29 @@ static func prepare(world, target: Dictionary, expired: Dictionary, year: Varian
|
||||
var products: Dictionary = {}
|
||||
for destination in plan.outputs:
|
||||
var pop: Dictionary = plan.outputs[destination]
|
||||
var count: Variant = total(pop)
|
||||
if rules.has(destination):
|
||||
if E.ne(destination, "Ct"):
|
||||
if E.ne(destination, "Ct") and E.gt(count, 0):
|
||||
var profile_drift: int = world.profile_start()
|
||||
pop = drift(target, E.add(E.add(E.add(id, ":"), destination), ":drift"), pop, rules[id].ethics, (E.eq(destination, id) and rules[id].diplomacy))
|
||||
pop = drift(target, E.add(E.add(E.add(id, ":"), destination), ":drift"), pop, rules[id].ethics, (E.eq(destination, id) and rules[id].diplomacy), false, count)
|
||||
world.profile_end("ethics_drift", profile_drift)
|
||||
if (not incoming.has(destination)): incoming[destination] = {}
|
||||
add(incoming[destination], pop)
|
||||
products[destination] = total(pop)
|
||||
products[destination] = count
|
||||
if (State.is_space_civilization(id, rules) and E.eq(world.catalog.entities[destination].group_as, "Planet")):
|
||||
planetary[destination] = E.add(planetary.get(destination, 0), total(pop))
|
||||
planetary[destination] = E.add(planetary.get(destination, 0), count)
|
||||
expired[id] = [{"amount": plan.amount, "from": plan.from, "to": E.maximum(plan.from, plan.to), "products": products}]
|
||||
world.profile_end("civilization_prepare", profile_started)
|
||||
return {"remaining": remaining, "incoming": incoming, "planetary": planetary, "colonies": colonies}
|
||||
return {"active": true, "remaining": remaining, "incoming": incoming, "planetary": planetary, "colonies": colonies}
|
||||
|
||||
static func finish(world, target: Dictionary, transaction: Dictionary, year: Variant, rules: Dictionary, budget_timer: RefCounted = null) -> void:
|
||||
var profile_started: int = world.profile_start()
|
||||
if not transaction.get("active", true):
|
||||
State.reconcile(world, target, rules)
|
||||
if not target.has("civilization_stats"): target.civilization_stats = {}
|
||||
target.civilization_stats.worship = worship(world, target, rules)
|
||||
world.profile_end("civilization_finish", profile_started)
|
||||
return
|
||||
target.ethics = transaction.remaining
|
||||
for id in transaction.incoming:
|
||||
if (not target.ethics.has(id)): target.ethics[id] = {}
|
||||
@@ -209,11 +387,7 @@ static func finish(world, target: Dictionary, transaction: Dictionary, year: Var
|
||||
else:
|
||||
colonize(world, target, transaction.colonies, year, rules, physical)
|
||||
State.reconcile(world, target, rules, physical if transaction.colonies.is_empty() else {})
|
||||
var worship: Variant = 0
|
||||
for id in rules:
|
||||
if (not rules[id].worships): continue
|
||||
for ethic in target.ethics[id]: worship = E.add(worship, E.multiply(target.ethics[id][ethic], E.of(rules[id].ethics[ethic].worship)))
|
||||
target.civilization_stats.worship = worship
|
||||
target.civilization_stats.worship = worship(world, target, rules)
|
||||
world.profile_end("civilization_finish", profile_started)
|
||||
|
||||
static func colonize(world, target: Dictionary, colonies: Array, year: Variant, rules: Dictionary, physical: Dictionary = {}, budget_timer: RefCounted = null) -> void:
|
||||
|
||||
@@ -10,14 +10,19 @@ static func is_space_civilization(id: String, rules: Dictionary) -> bool:
|
||||
|
||||
# Preserve any existing ethics. New populations start equally distributed;
|
||||
# reductions (including blasts) are proportional and never leave negative counts.
|
||||
static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> void:
|
||||
static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}, only_ids: Array = []) -> void:
|
||||
if (not target.has("ethics")):
|
||||
target.ethics = {}
|
||||
if physical.is_empty():
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
for id in rules:
|
||||
var ids: Variant = rules if only_ids.is_empty() else only_ids
|
||||
for id in ids:
|
||||
if not rules.has(id): continue
|
||||
var count: Variant = physical.get(id, 0)
|
||||
var current: Dictionary = target.ethics.get(id, {})
|
||||
var cached: Dictionary = world.ledger.reconciled_ethics.get(id, {})
|
||||
if not cached.is_empty() and E.eq(cached.count, count) and E.eq(cached.population, current):
|
||||
continue
|
||||
var total: Variant = 0
|
||||
for ethic in ETHICS:
|
||||
current[ethic] = E.maximum(0, current.get(ethic, 0))
|
||||
@@ -31,6 +36,7 @@ static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Di
|
||||
for i in ETHICS.size():
|
||||
current[ETHICS[i]] = E.add(current[ETHICS[i]], E.add(base, ((1 if E.lt(i, rest) else 0))))
|
||||
target.ethics[id] = current
|
||||
world.ledger.reconciled_ethics[id] = {"count": count, "population": current.duplicate()}
|
||||
|
||||
static func detach_space_civilizations(target: Dictionary, rules: Dictionary) -> void:
|
||||
for held : Dictionary in target.binds.values():
|
||||
|
||||
@@ -38,6 +38,7 @@ func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefC
|
||||
next.version = SimulationTime.STATE_VERSION
|
||||
next.speed = clampi(E.integer(previous.speed), 1, Global.SPEED_LIMIT)
|
||||
next.speed_in_year = E.power(10, E.subtract(next.speed, 1))
|
||||
world.batches.coarsen(next)
|
||||
var from_year: Variant = previous.time.year
|
||||
var to_year: Variant = processed_year
|
||||
if E.gt(to_year, from_year):
|
||||
@@ -72,38 +73,48 @@ func evolve_batches(target: Dictionary, to_year: Variant, budget: RefCounted = n
|
||||
var _keep_alive: RefCounted = self
|
||||
# A long cycle may contain several generations; preserve actual birth intervals.
|
||||
var completed: Dictionary = {}
|
||||
while await evolve_batch_generation(target, to_year, completed, budget):
|
||||
var context: Dictionary = {
|
||||
"life_modifiers": WorldParameters.life_modifiers(target),
|
||||
"enlightenment": WorldParameters.value(target, "Enlightenment"),
|
||||
"explosiveness": WorldParameters.effective(target, "Exp")
|
||||
}
|
||||
while await evolve_batch_generation(target, to_year, completed, budget, context):
|
||||
if budget != null and not await budget.pause(): return
|
||||
|
||||
|
||||
func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Dictionary = { }, budget: RefCounted = null) -> bool:
|
||||
func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Dictionary = { }, budget: RefCounted = null, context: Dictionary = {}) -> bool:
|
||||
var _keep_alive: RefCounted = self
|
||||
# Apply direct completions at the original next-generation boundary, so
|
||||
# binding transfers, the blast environment and capacity checks keep order.
|
||||
if budget != null and not await budget.pause(): return false
|
||||
var completed_now: Dictionary = completed.duplicate(true)
|
||||
var completed_now: Dictionary = completed.duplicate()
|
||||
completed.clear()
|
||||
if context.is_empty():
|
||||
context = {"life_modifiers": WorldParameters.life_modifiers(target), "enlightenment": WorldParameters.value(target, "Enlightenment"), "explosiveness": WorldParameters.effective(target, "Exp")}
|
||||
var quantities: Dictionary = world.ledger.physical_amounts(target)
|
||||
var expired: Dictionary = {}
|
||||
var preview: Dictionary = {"batches": {}}
|
||||
for id in target.batches:
|
||||
if (world.catalog.entities.has(id) and E.gt(world.catalog.entities[id].destroy_level, 0)):
|
||||
preview.batches[id] = target.batches[id].duplicate(true)
|
||||
for id in target.batches.keys():
|
||||
var withdrawn: Dictionary = {}
|
||||
var batch_ids: Array = target.batches.keys()
|
||||
var planets_changed: bool = false
|
||||
var profile_withdraw: int = world.profile_start()
|
||||
for id in batch_ids:
|
||||
if ((not world.catalog.entities.has(id)) or E.le(world.catalog.entities[id].destroy_level, 0)):
|
||||
continue
|
||||
var dying: Variant = 0
|
||||
for part : Dictionary in completed_now.get(id, []):
|
||||
dying = E.add(dying, part.amount)
|
||||
for part : Dictionary in world.batches.withdraw_batches(preview, id, to_year):
|
||||
withdrawn[id] = world.batches.withdraw_batches(target, id, to_year)
|
||||
for part : Dictionary in withdrawn[id]:
|
||||
dying = E.add(dying, part.amount)
|
||||
if E.gt(quantities.get(id, 0), 0):
|
||||
world.stellar.destroy_planets(target, id, E.divide(dying, quantities[id]), world.catalog.entities[id].destroy_level)
|
||||
quantities = world.ledger.physical_amounts(target)
|
||||
planets_changed = world.stellar.destroy_planets(target, id, E.divide(dying, quantities[id]), world.catalog.entities[id].destroy_level) or planets_changed
|
||||
world.profile_end("generation_withdrawal", profile_withdraw)
|
||||
if planets_changed: quantities = world.ledger.physical_amounts(target)
|
||||
# Withdraw all types before creating successors: iteration order cannot affect them.
|
||||
for id in target.batches.keys():
|
||||
for id in batch_ids:
|
||||
if world.catalog.entities.has(id):
|
||||
expired[id] = E.add(completed_now.get(id, []), world.batches.withdraw_batches(target, id, to_year))
|
||||
var parts: Array = withdrawn[id] if withdrawn.has(id) else world.batches.withdraw_batches(target, id, to_year)
|
||||
expired[id] = E.add(completed_now.get(id, []), parts)
|
||||
var has_expired: bool = false
|
||||
for parts : Array in expired.values():
|
||||
if (not parts.is_empty()):
|
||||
@@ -111,22 +122,28 @@ func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Di
|
||||
break
|
||||
if (not has_expired):
|
||||
return false
|
||||
# This per-generation summary is valid until the next transition mutates
|
||||
# the cohort set. Reuse it for civilization planning and transfer ratios.
|
||||
var expired_totals: Dictionary = {}
|
||||
for id in expired:
|
||||
var total: Variant = 0
|
||||
for part: Dictionary in expired[id]: total = E.add(total, part.amount)
|
||||
expired_totals[id] = total
|
||||
var fractions: Dictionary = {}
|
||||
var outputs: Dictionary = {}
|
||||
var current_quantities: Dictionary = quantities
|
||||
var star_budget: Dictionary = CivilizationState.stellar_budget(world, target, Global.CivilizationRules, current_quantities)
|
||||
var civilization_transaction: Dictionary = await CivilizationEvolution.prepare(world, target, expired, to_year, star_budget, Global.CivilizationRules, current_quantities, budget)
|
||||
var civilization_transaction: Dictionary = await CivilizationEvolution.prepare(world, target, expired, to_year, star_budget, Global.CivilizationRules, current_quantities, budget, expired_totals)
|
||||
if budget != null and budget.cancelled: return false
|
||||
var profile_products: int = world.profile_start()
|
||||
for id in expired:
|
||||
if budget != null and not await budget.pause(): return false
|
||||
var total: Variant = 0
|
||||
for part : Dictionary in expired[id]:
|
||||
total = E.add(total, part.amount)
|
||||
var total: Variant = expired_totals[id]
|
||||
fractions[id] = (E.clamp_value(E.divide(total, quantities[id]), 0, 1) if E.gt(quantities.get(id, 0), 0) else 0)
|
||||
var combined: Dictionary = {}
|
||||
for part : Dictionary in expired[id]:
|
||||
if (not part.has("products")):
|
||||
part.products = world.stellar.evolution_products(target, world.catalog.entities[id], part.amount)
|
||||
part.products = world.stellar.evolution_products(target, world.catalog.entities[id], part.amount, context)
|
||||
for successor in part.products.keys():
|
||||
if Global.CivilizationRules.has(id):
|
||||
continue
|
||||
@@ -141,19 +158,32 @@ func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Di
|
||||
if E.gt(total, 0):
|
||||
for successor in combined:
|
||||
outputs[id][successor] = E.divide(combined[successor], total)
|
||||
world.profile_end("generation_products", profile_products)
|
||||
|
||||
var affected: Dictionary = {}
|
||||
for id in fractions:
|
||||
if E.le(fractions[id], 0): continue
|
||||
affected[id] = true
|
||||
for held in target.binds.get(id, {}): affected[held] = true
|
||||
for successor in outputs[id]: affected[successor] = true
|
||||
var incoming: Dictionary = world.ledger.transfer_evolution(target, fractions, outputs)
|
||||
|
||||
var new_owners: Dictionary = {}
|
||||
var blast_power: Variant = 0
|
||||
var environment: Dictionary = world.environment.stellar_environment(target)
|
||||
for parts: Array in expired.values():
|
||||
for part: Dictionary in parts:
|
||||
blast_power = E.add(blast_power, part.products.get("blast", 0))
|
||||
# A zero blast does not need density/mass/volume recalculated. For an actual
|
||||
# blast retain the original snapshot before crediting newborn populations.
|
||||
var environment: Dictionary = world.environment.stellar_environment(target) if E.gt(blast_power, 0) else {}
|
||||
var resolution: Variant = world.batches.temporal_resolution(target)
|
||||
for id in expired:
|
||||
for part : Dictionary in expired[id]:
|
||||
for successor in part.products:
|
||||
if budget != null and not await budget.pause(): return false
|
||||
var amount: Variant = part.products[successor]
|
||||
if E.le(amount, 0): continue
|
||||
if E.eq(successor, "blast"):
|
||||
blast_power = E.add(blast_power, amount)
|
||||
continue
|
||||
if (not world.catalog.entities.has(successor)):
|
||||
continue
|
||||
@@ -162,27 +192,38 @@ func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Di
|
||||
else:
|
||||
amount = world.ledger.credit(target, successor, amount)
|
||||
var produces: bool = not world.automatic.yearly_products(world.catalog.entities[successor]).is_empty()
|
||||
var before_batches: Array = target.batches.get(successor, []).duplicate(true) if produces else []
|
||||
var newborn_adjustment: Dictionary = {}
|
||||
var cutoff: Variant = (to_year if world.stellar.uses_short_lived_fast_path(successor) else E.neg(INF))
|
||||
var born_from: Variant = part.from
|
||||
var born_to: Variant = part.to
|
||||
if E.gt(resolution, 0) and (Global.CivilizationRules.has(successor) or not world.catalog.entities[successor].life_rule.is_empty()):
|
||||
# Biological stages (including C0.0/C0.1) and civilizations
|
||||
# share the coarse clock. Historical retry intervals must
|
||||
# not retrigger their early population within the same bucket.
|
||||
born_to = E.multiply(E.ceil_value(E.divide(born_to, resolution)), resolution)
|
||||
born_from = born_to
|
||||
var profile_newborn: int = world.profile_start()
|
||||
var integral: Variant
|
||||
if budget != null:
|
||||
integral = await world.batches.append_batch(target, successor, amount, part.from, part.to, completed, cutoff, budget)
|
||||
integral = await world.batches.append_batch(target, successor, amount, born_from, born_to, completed, cutoff, budget, newborn_adjustment if produces else null, to_year)
|
||||
if budget.cancelled: return false
|
||||
else:
|
||||
integral = world.batches.append_batch(target, successor, amount, part.from, part.to, completed, cutoff)
|
||||
integral = world.batches.append_batch(target, successor, amount, born_from, born_to, completed, cutoff, null, newborn_adjustment if produces else null, to_year)
|
||||
if produces:
|
||||
world.automatic.produce_newborn_lifetime(target, successor, amount, part.from, part.to, to_year, before_batches, integral)
|
||||
world.automatic.produce_newborn_lifetime(target, successor, amount, born_from, born_to, to_year, newborn_adjustment.get("past_years", 0), integral)
|
||||
world.profile_end("newborn_batches", profile_newborn)
|
||||
new_owners[successor] = E.add(new_owners.get(successor, 0), amount)
|
||||
world.ledger.accept_transfers(target, incoming)
|
||||
world.ledger.bind_new_owners(target, new_owners)
|
||||
world.ledger.round_object_allocations(target)
|
||||
for owner in new_owners:
|
||||
for resource in world.catalog.entities[owner].bind:
|
||||
for member in world.catalog.members(resource): affected[member] = true
|
||||
world.ledger.round_object_allocations(target, affected)
|
||||
if budget != null:
|
||||
await CivilizationEvolution.finish(world, target, civilization_transaction, to_year, Global.CivilizationRules, budget)
|
||||
if budget.cancelled: return false
|
||||
else:
|
||||
CivilizationEvolution.finish(world, target, civilization_transaction, to_year, Global.CivilizationRules)
|
||||
world.stellar.apply_blast(target, blast_power, environment)
|
||||
if E.gt(blast_power, 0): world.stellar.apply_blast(target, blast_power, environment)
|
||||
CivilizationState.enforce_capacity(world, target, Global.CivilizationRules, to_year)
|
||||
return true
|
||||
|
||||
@@ -5,11 +5,13 @@ const E = preload("res://world/owniverse/math/exact.gd")
|
||||
var pending_amounts: Dictionary [ String , Variant ] = {}
|
||||
var deferred_updates: bool = false
|
||||
var dirty: bool = true
|
||||
var revision: int = 0
|
||||
|
||||
func refresh(state: Dictionary, catalog, force: bool = false) -> void:
|
||||
dirty = true
|
||||
if (deferred_updates and (not force)):
|
||||
return
|
||||
revision += 1
|
||||
pending_amounts = amounts(state, catalog)
|
||||
dirty = false
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
|
||||
const QuantityMath = preload("res://world/owniverse/quantity_math.gd")
|
||||
|
||||
func life_products(target: Dictionary, entity: OwniverseEntity, count: Variant) -> Dictionary:
|
||||
var rule: Dictionary = entity.life_rule
|
||||
func life_products(target: Dictionary, entity: OwniverseEntity, count: Variant, context: Dictionary = {}) -> Dictionary:
|
||||
var plan: Dictionary = entity.generation_plan
|
||||
var pending: Dictionary = {}
|
||||
var modifiers: Dictionary = WorldParameters.life_modifiers(target)
|
||||
var probabilities: Dictionary = {"progress": E.multiply(E.multiply(E.of(rule.progress), modifiers.progress), WorldParameters.value(target, "Enlightenment")), "degradation": E.multiply(E.of(rule.degradation), modifiers.degradation)}
|
||||
var modifiers: Dictionary = context.life_modifiers if context.has("life_modifiers") else WorldParameters.life_modifiers(target)
|
||||
var enlightenment: Variant = context.enlightenment if context.has("enlightenment") else WorldParameters.value(target, "Enlightenment")
|
||||
var probabilities: Dictionary = {"progress": E.multiply(E.multiply(plan.life_progress, modifiers.progress), enlightenment), "degradation": E.multiply(plan.life_degradation, modifiers.degradation)}
|
||||
var probability_sum: Variant = E.maximum(1, E.add(probabilities.progress, probabilities.degradation))
|
||||
if (not target.has("life_remainders")):
|
||||
target.life_remainders = {}
|
||||
@@ -21,13 +22,10 @@ func life_products(target: Dictionary, entity: OwniverseEntity, count: Variant)
|
||||
var key: String = E.add(E.add(entity.id, ":"), channel)
|
||||
target.life_remainders[key] = E.maximum(0, E.subtract(target.life_remainders[key], assigned[channel]))
|
||||
var result: Dictionary = {entity.id: E.subtract(E.subtract(count, assigned.progress), assigned.degradation)}
|
||||
result[rule.next] = assigned.progress
|
||||
var weights: Dictionary = {}
|
||||
result[plan.life_next] = assigned.progress
|
||||
var weights: Dictionary = plan.life_fall
|
||||
var weight_sum: Variant = 0
|
||||
for term in str(rule.fall).split("^"):
|
||||
var pair: PackedStringArray = term.split(":")
|
||||
weights[pair[0]] = E.of(pair[1])
|
||||
weight_sum = E.add(weight_sum, E.of(pair[1]))
|
||||
for weight in weights.values(): weight_sum = E.add(weight_sum, weight)
|
||||
var requests: Dictionary = {}
|
||||
for id in weights:
|
||||
var key: String = E.add(E.add(entity.id, ":fall:"), id)
|
||||
|
||||
@@ -47,6 +47,9 @@ static func subtract(a: Array, b: Array) -> Array:
|
||||
return trim(result)
|
||||
|
||||
static func multiply(a: Array, b: Array) -> Array:
|
||||
if a == [0] or b == [0]: return [0]
|
||||
if a == [1]: return b.duplicate()
|
||||
if b == [1]: return a.duplicate()
|
||||
var result: Array = []
|
||||
result.resize(a.size() + b.size())
|
||||
result.fill(0)
|
||||
@@ -64,6 +67,20 @@ static func divmod(a: Array, b: Array) -> Array:
|
||||
assert(b != [0], "Division by zero")
|
||||
if b == [1]: return [a.duplicate(), [0]]
|
||||
if compare(a, b) < 0: return [[0], a.duplicate()]
|
||||
# Decimal ratios often share entire base-1e9 factors. Cancel them before
|
||||
# long division; restore the remainder's original scale afterwards.
|
||||
var shared: int = 0
|
||||
while shared < mini(a.size(), b.size()) - 1 and a[shared] == 0 and b[shared] == 0:
|
||||
shared += 1
|
||||
if shared:
|
||||
var reduced: Array = divmod(a.slice(shared), b.slice(shared))
|
||||
if reduced[1] != [0]:
|
||||
var restored: Array = []
|
||||
restored.resize(shared)
|
||||
restored.fill(0)
|
||||
restored.append_array(reduced[1])
|
||||
reduced[1] = restored
|
||||
return reduced
|
||||
if b.size() == 1:
|
||||
var quotient: Array = []
|
||||
quotient.resize(a.size())
|
||||
|
||||
@@ -170,6 +170,8 @@ static func absolute(a): return neg(a) if lt(a, 0) else a
|
||||
|
||||
static func add(a, b):
|
||||
if not numeric(a) or not numeric(b): return a + b
|
||||
if b is int and b == 0: return of(a)
|
||||
if a is int and a == 0: return of(b)
|
||||
if a is int and b is int:
|
||||
if (b >= 0 and a <= MAX_INT - b) or (b < 0 and a >= MIN_INT - b): return a + b
|
||||
if not finite(a) or not finite(b): return approx(a) + approx(b)
|
||||
@@ -190,6 +192,7 @@ static func add(a, b):
|
||||
|
||||
static func subtract(a, b):
|
||||
if not numeric(a) or not numeric(b): return a - b
|
||||
if b is int and b == 0: return of(a)
|
||||
if a is int and b is int:
|
||||
if (b >= 0 and a >= MIN_INT + b) or (b < 0 and a <= MAX_INT + b): return a - b
|
||||
return add(a, neg(b))
|
||||
@@ -202,6 +205,10 @@ static func multiply(a, b):
|
||||
if eq(b, 1): return of(a)
|
||||
if a is int and b is int and a != MIN_INT and b != MIN_INT:
|
||||
if absi(a) <= MAX_INT / absi(b): return a * b
|
||||
if (a is int or (a is Value and a.fraction == 0.0)) and (b is int or (b is Value and b.fraction == 0.0)):
|
||||
var whole_a: Array = _blocks(a)
|
||||
var whole_b: Array = _blocks(b)
|
||||
return _compose(Big.multiply(whole_a[0], whole_b[0]), 0.0, whole_a[2] * whole_b[2])
|
||||
var x: Array = parts(a)
|
||||
var y: Array = parts(b)
|
||||
return make(Big.multiply(x[0], y[0]), Big.multiply(x[1], y[1]), x[2] * y[2])
|
||||
@@ -217,6 +224,38 @@ static func mul_div(a, b, divisor):
|
||||
assert(z[0] != [0], "Division by zero")
|
||||
return make(Big.multiply(Big.multiply(x[0], y[0]), z[1]), Big.multiply(Big.multiply(x[1], y[1]), z[0]), x[2] * y[2] * z[2])
|
||||
|
||||
## Reuse the count and divisor decomposition across all quotas.
|
||||
static func mul_div_many(a, values: Array, divisor) -> Array:
|
||||
var result: Array = []
|
||||
if not finite(a) or not finite(divisor):
|
||||
for value in values: result.append(mul_div(a, value, divisor))
|
||||
return result
|
||||
var x: Array = parts(a)
|
||||
var z: Array = parts(divisor)
|
||||
assert(z[0] != [0], "Division by zero")
|
||||
for value in values:
|
||||
if not finite(value) or (a is int and value is int and a != MIN_INT and value != MIN_INT and (value == 0 or absi(a) <= MAX_INT / absi(value))):
|
||||
result.append(mul_div(a, value, divisor))
|
||||
continue
|
||||
var y: Array = parts(value)
|
||||
result.append(make(Big.multiply(Big.multiply(x[0], y[0]), z[1]), Big.multiply(Big.multiply(x[1], y[1]), z[0]), x[2] * y[2] * z[2]))
|
||||
return result
|
||||
|
||||
## Cancel an integer population from quotas, retaining the original float ratio.
|
||||
static func population_quotas(count, weights: Array, factor: int) -> Array:
|
||||
var denominator = multiply(count, factor)
|
||||
if ne(count, floor_value(count)) or le(count, 0): return mul_div_many(count, weights, denominator)
|
||||
var population: Array = _blocks(count)[0]
|
||||
var denominator_blocks: Array = Big.multiply(population, [factor])
|
||||
var result: Array = []
|
||||
for weight in weights:
|
||||
if ne(weight, floor_value(weight)) or lt(weight, 0) or (count is int and weight is int and count != MIN_INT and weight != MIN_INT and (weight == 0 or absi(count) <= MAX_INT / absi(weight))):
|
||||
result.append(mul_div(count, weight, denominator))
|
||||
else:
|
||||
var qr: Array = Big.divmod(_blocks(weight)[0], [factor])
|
||||
result.append(_compose(qr[0], _fraction_ratio(Big.multiply(qr[1], population), denominator_blocks)))
|
||||
return result
|
||||
|
||||
## Sum of nonnegative linear quotas rounded down separately at each boundary.
|
||||
static func floor_sum(count, step, offset, divisor):
|
||||
var n: Array = parts(count)
|
||||
|
||||
@@ -4,7 +4,99 @@ const CivilizationState = preload("res://world/owniverse/civilization_state.gd")
|
||||
const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
|
||||
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
|
||||
|
||||
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
|
||||
|
||||
func window_duration(target: Dictionary, lifespan: Variant) -> Variant:
|
||||
if E.le(lifespan, 10): return lifespan
|
||||
var base: Variant = E.divide(lifespan, Global.batch_count)
|
||||
# At high speed one tenth of a real second is the temporal resolution.
|
||||
# Short stellar explosions retain their original event windows.
|
||||
return E.maximum(base, temporal_resolution(target))
|
||||
|
||||
func coarsen(target: Dictionary) -> void:
|
||||
if int(target.get("speed", 1)) < 4: return
|
||||
for id in target.batches:
|
||||
if target.batches[id].size() <= 1: continue
|
||||
if not world.catalog.entities.has(id): continue
|
||||
var lifespan: Variant = effective_lifespan(target, id)
|
||||
if E.le(lifespan, 10): continue
|
||||
var width: Variant = window_duration(target, lifespan)
|
||||
if E.le(width, E.divide(lifespan, Global.batch_count)): continue
|
||||
var groups: Dictionary = {}
|
||||
var result: Array = []
|
||||
for batch: Dictionary in target.batches[id]:
|
||||
# Never move an already processed boundary backwards.
|
||||
var start: Variant = E.maximum(batch.start_year, batch.get("processed_until", batch.start_year))
|
||||
var key: String = str(E.floor_value(E.divide(start, width)))
|
||||
if not groups.has(key):
|
||||
var combined: Dictionary = batch.duplicate()
|
||||
groups[key] = combined
|
||||
result.append(combined)
|
||||
else:
|
||||
var combined: Dictionary = groups[key]
|
||||
combined.amount = E.add(combined.amount, batch.amount)
|
||||
combined.start_year = E.minimum(combined.start_year, batch.start_year)
|
||||
combined.end_year = E.maximum(combined.end_year, batch.end_year)
|
||||
combined.created_from = E.minimum(combined.get("created_from", start), batch.get("created_from", start))
|
||||
combined.created_to = E.maximum(combined.get("created_to", batch.end_year), batch.get("created_to", batch.end_year))
|
||||
combined.withdraw_remainder = E.add(combined.get("withdraw_remainder", 0), batch.get("withdraw_remainder", 0))
|
||||
if combined.has("processed_until") or batch.has("processed_until"):
|
||||
combined.processed_until = E.maximum(combined.get("processed_until", combined.start_year), batch.get("processed_until", batch.start_year))
|
||||
if result.size() < target.batches[id].size():
|
||||
result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return E.lt(a.get("created_from", a.start_year), b.get("created_from", b.start_year)))
|
||||
target.batches[id] = result
|
||||
|
||||
# Indices belong to one transaction. Withdrawal replaces the cohort array,
|
||||
# invalidating that entity's index; amount changes leave its keys intact.
|
||||
var _indexed_target: Dictionary = {}
|
||||
var _batch_indices: Dictionary = {}
|
||||
var _ready_target: Dictionary = {}
|
||||
var _ready_indices: Dictionary = {}
|
||||
var _instant_target: Dictionary = {}
|
||||
var _instant_appends: Dictionary = {}
|
||||
var _static_spans: Dictionary = {}
|
||||
var _star_spans: Dictionary = {}
|
||||
|
||||
func effective_lifespan(target: Dictionary, id: String) -> Variant:
|
||||
var entity: OwniverseEntity = world.catalog.entities[id]
|
||||
if Global.CivilizationRules.has(id):
|
||||
if CivilizationState.is_space_civilization(id, Global.CivilizationRules):
|
||||
return CivilizationState.effective_span(world, target, id, Global.CivilizationRules)
|
||||
return Global.CivilizationRules[id].span
|
||||
var lifespan: Variant = _static_spans.get(id, entity.lifespan)
|
||||
if not _static_spans.has(id): _static_spans[id] = lifespan
|
||||
if entity.group_as != "Star": return lifespan
|
||||
var burnability: Variant = WorldParameters.effective(target, "Bur")
|
||||
var signature: String = id + ":" + str(burnability)
|
||||
if _star_spans.has(signature): return _star_spans[signature]
|
||||
var result: Variant = E.divide(lifespan, burnability)
|
||||
if _star_spans.size() >= 256: _star_spans.clear()
|
||||
_star_spans[signature] = result
|
||||
return result
|
||||
|
||||
func batch_index(target: Dictionary, id: String, batches: Array) -> Dictionary:
|
||||
if not is_same(_indexed_target, target):
|
||||
_indexed_target = target
|
||||
_batch_indices.clear()
|
||||
var cached: Dictionary = _batch_indices.get(id, {})
|
||||
if not cached.is_empty() and is_same(cached.batches, batches) and cached.count == batches.size():
|
||||
return cached.starts
|
||||
for i in range(1, batches.size()):
|
||||
if E.lt(batches[i].get("created_from", -INF), batches[i - 1].get("created_from", -INF)):
|
||||
batches.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return E.lt(a.get("created_from", -INF), b.get("created_from", -INF)))
|
||||
break
|
||||
var starts: Dictionary = {}
|
||||
for existing: Dictionary in batches:
|
||||
var key: String = str(existing.get("created_from", -INF))
|
||||
if not starts.has(key): starts[key] = []
|
||||
starts[key].append(existing)
|
||||
_batch_indices[id] = {"batches": batches, "count": batches.size(), "starts": starts}
|
||||
return starts
|
||||
|
||||
func scale_batches(target: Dictionary, id: String, factor: Variant) -> void:
|
||||
_ready_indices.erase(id)
|
||||
var batches: Array = target.batches.get(id, [])
|
||||
if batches.is_empty():
|
||||
return
|
||||
@@ -34,26 +126,29 @@ 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) -> 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) -> Variant:
|
||||
var _keep_alive: RefCounted = self
|
||||
if ((E.le(amount, 0) or (not world.catalog.entities.has(id))) or E.le(world.catalog.entities[id].lifespan, 0)):
|
||||
return NAN
|
||||
if E.lt(created_to, created_from):
|
||||
return NAN
|
||||
_ready_indices.erase(id)
|
||||
if (not target.batches.has(id)):
|
||||
target.batches[id] = []
|
||||
if (not target.has("batch_origins")):
|
||||
target.batch_origins = {}
|
||||
var batches: Array = target.batches[id]
|
||||
var lifespan: Variant = CivilizationState.effective_span(world, target, id, Global.CivilizationRules)
|
||||
if E.eq(world.catalog.entities[id].group_as, "Star"):
|
||||
lifespan = E.divide(lifespan, WorldParameters.effective(target, "Bur"))
|
||||
var batch_duration: Variant = (E.divide(lifespan, Global.batch_count) if E.gt(lifespan, 10) else lifespan)
|
||||
var lifespan: Variant = effective_lifespan(target, id)
|
||||
var batch_duration: Variant = window_duration(target, lifespan)
|
||||
if E.le(batch_duration, 0):
|
||||
return NAN
|
||||
if (not target.batch_origins.has(id)):
|
||||
target.batch_origins[id] = (batches[0].get("created_from", created_from) if (not batches.is_empty()) else created_from)
|
||||
var origin: Variant = target.batch_origins[id]
|
||||
if E.gt(temporal_resolution(target), 0) and (Global.CivilizationRules.has(id) or not world.catalog.entities[id].life_rule.is_empty()):
|
||||
# Match successor birth buckets, regardless of the old saved origin.
|
||||
# Otherwise a rounded birth can be placed back into an earlier window.
|
||||
origin = 0
|
||||
var cursor: Variant = created_from
|
||||
var duration: Variant = E.subtract(created_to, created_from)
|
||||
var remaining_amount: Variant = amount
|
||||
@@ -62,18 +157,23 @@ func append_batch(target: Dictionary, id: String, amount: Variant, created_from:
|
||||
var completed_to: Variant = E.neg(INF)
|
||||
# Subtract each completed window's lifetime production exactly.
|
||||
var remaining_object_years: Variant = (E.multiply(amount, E.maximum(0, E.subtract(completed_before, E.multiply((E.add(created_from, created_to)), E.of("0.5"))))) if E.finite(completed_before) else NAN)
|
||||
if not is_same(_instant_target, target):
|
||||
_instant_target = target
|
||||
_instant_appends.clear()
|
||||
var instant: Dictionary = _instant_appends.get(id, {})
|
||||
if E.eq(duration, 0) and not instant.is_empty() and is_same(instant.batches, batches) and instant.count == batches.size() and E.eq(instant.birth, created_from) and E.eq(instant.lifespan, lifespan) and E.eq(instant.width, batch_duration) and E.eq(instant.origin, origin) and E.gt(instant.batch.end_year, completed_before):
|
||||
instant.batch.amount = E.add(instant.batch.amount, amount)
|
||||
if newborn_adjustment != null and E.finite(newborn_until):
|
||||
newborn_adjustment.past_years = E.add(newborn_adjustment.get("past_years", 0), E.multiply(amount, expired_years(newborn_until, instant.batch.start_year, instant.batch.end_year)))
|
||||
return remaining_object_years
|
||||
# Advance an exact window index rather than repeatedly finding a boundary.
|
||||
var window_index: Variant = E.floor_value(E.divide((E.subtract(cursor, origin)), batch_duration))
|
||||
if E.le(E.add(origin, E.multiply((E.add(window_index, 1)), batch_duration)), cursor):
|
||||
window_index = E.add(window_index, 1)
|
||||
var batches_by_start: Dictionary = {}
|
||||
var batches_by_start: Dictionary = batch_index(target, id, batches)
|
||||
var order_changed: bool = false
|
||||
var skip_empty_expired: bool = E.gt(duration, 0) and E.finite(completed_before) and E.eq(amount, E.floor_value(amount))
|
||||
var sparse_windows: bool = E.gt(duration, 0) and E.gt(amount, 0) and E.gt(duration, E.multiply(batch_duration, amount)) and not skip_empty_expired
|
||||
for existing : Dictionary in batches:
|
||||
var start: Variant = existing.get("created_from", E.neg(INF))
|
||||
if (not batches_by_start.has(str(start))):
|
||||
batches_by_start[str(start)] = []
|
||||
batches_by_start[str(start)].append(existing)
|
||||
while true:
|
||||
if budget != null and not await budget.pause(): return NAN
|
||||
# A zero cohort is discarded by withdraw_batches. Jump straight to the
|
||||
@@ -145,17 +245,24 @@ func append_batch(target: Dictionary, id: String, amount: Variant, created_from:
|
||||
break
|
||||
if batch.is_empty():
|
||||
batch = {"created_from": window_start, "created_to": window_end, "start_year": E.add(window_start, lifespan), "end_year": E.add(window_end, lifespan), "amount": 0}
|
||||
order_changed = order_changed or (not batches.is_empty() and E.lt(window_start, batches.back().created_from))
|
||||
batches.append(batch)
|
||||
if (not batches_by_start.has(str(window_start))):
|
||||
batches_by_start[str(window_start)] = []
|
||||
batches_by_start[str(window_start)].append(batch)
|
||||
batch.amount = E.add(batch.amount, part_amount)
|
||||
if newborn_adjustment != null and E.finite(newborn_until):
|
||||
newborn_adjustment.past_years = E.add(newborn_adjustment.get("past_years", 0), E.multiply(part_amount, expired_years(newborn_until, batch.start_year, batch.end_year)))
|
||||
if E.eq(duration, 0):
|
||||
_instant_appends[id] = {"batches": batches, "count": batches.size(), "birth": created_from, "lifespan": lifespan, "width": batch_duration, "origin": origin, "batch": batch}
|
||||
remaining_amount = E.subtract(remaining_amount, part_amount)
|
||||
if (E.eq(duration, 0) or E.ge(part_end, created_to)):
|
||||
break
|
||||
cursor = window_end
|
||||
window_index = E.add(window_index, 1)
|
||||
batches.sort_custom(func ( a : Dictionary , b : Dictionary ) -> bool : return E.lt(a.created_from, b.created_from))
|
||||
if order_changed:
|
||||
batches.sort_custom(func ( a : Dictionary , b : Dictionary ) -> bool : return E.lt(a.created_from, b.created_from))
|
||||
_batch_indices[id].count = batches.size()
|
||||
if E.gt(completed_amount, 0):
|
||||
if (not completed.has(id)): completed[id] = []
|
||||
completed[id].append({"amount": completed_amount, "from": completed_from, "to": completed_to})
|
||||
@@ -163,9 +270,16 @@ func append_batch(target: Dictionary, id: String, amount: Variant, created_from:
|
||||
|
||||
|
||||
func withdraw_batches(target: Dictionary, id: String, to_year: Variant) -> Array:
|
||||
if not is_same(_ready_target, target):
|
||||
_ready_target = target
|
||||
_ready_indices.clear()
|
||||
var batches: Array = target.batches.get(id, [])
|
||||
var ready: Dictionary = _ready_indices.get(id, {})
|
||||
if not ready.is_empty() and is_same(ready.batches, batches) and ready.count == batches.size() and E.lt(to_year, ready.year): return []
|
||||
var expired: Array = []
|
||||
var remaining: Array = []
|
||||
for batch : Dictionary in target.batches.get(id, []):
|
||||
var next_year: Variant = INF
|
||||
for batch : Dictionary in batches:
|
||||
var start: Variant = E.maximum(batch.start_year, batch.get("processed_until", batch.start_year))
|
||||
var end: Variant = batch.end_year
|
||||
var amount: Variant = batch.amount
|
||||
@@ -173,6 +287,7 @@ func withdraw_batches(target: Dictionary, id: String, to_year: Variant) -> Array
|
||||
continue
|
||||
if (E.lt(to_year, start) or ((E.eq(to_year, start) and E.gt(end, start)))):
|
||||
remaining.append(batch)
|
||||
next_year = E.minimum(next_year, start)
|
||||
continue
|
||||
var consumed: Variant = amount
|
||||
var until: Variant = E.minimum(to_year, end)
|
||||
@@ -184,9 +299,11 @@ func withdraw_batches(target: Dictionary, id: String, to_year: Variant) -> Array
|
||||
batch.amount = E.maximum(0, E.subtract(amount, consumed))
|
||||
batch.processed_until = to_year
|
||||
remaining.append(batch)
|
||||
next_year = E.minimum(next_year, to_year)
|
||||
if E.gt(consumed, 0):
|
||||
expired.append({"amount": consumed, "from": start, "to": until})
|
||||
target.batches[id] = remaining
|
||||
_ready_indices[id] = {"batches": remaining, "count": remaining.size(), "year": next_year}
|
||||
return expired
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@ var _amount_snapshot: Dictionary = {}
|
||||
var _binds_snapshot: Dictionary = {}
|
||||
var _physical_cache: Dictionary = {}
|
||||
var _physical_revision: int = -1
|
||||
# Per-world derived caches: never serialized and never shared between worlds.
|
||||
var reconciled_ethics: Dictionary = {}
|
||||
var worship_products: Dictionary = {}
|
||||
var civilization_modifiers: Dictionary = {}
|
||||
var civilization_coefficients: Dictionary = {}
|
||||
|
||||
func physical_amounts(source: Dictionary) -> Dictionary:
|
||||
world.catalog._ensure_group_index()
|
||||
@@ -107,9 +112,10 @@ func credit(target: Dictionary, id: String, amount: Variant) -> Variant:
|
||||
return amount
|
||||
|
||||
|
||||
func round_object_allocations(target: Dictionary) -> void:
|
||||
var physical: Dictionary = physical_amounts(target)
|
||||
for id in physical:
|
||||
func round_object_allocations(target: Dictionary, affected: Variant = null) -> void:
|
||||
var ids: Variant = world.catalog.entities if affected == null else affected
|
||||
for id in ids:
|
||||
if not world.catalog.entities.has(id) or world.catalog.is_group(id): continue
|
||||
if (not is_discrete(id)):
|
||||
continue
|
||||
var slots: Array = [{"owner": "", "value": target.amount.get(id, 0)}]
|
||||
@@ -117,12 +123,17 @@ func round_object_allocations(target: Dictionary) -> void:
|
||||
if target.binds[owner_id].has(id):
|
||||
slots.append({"owner": owner_id, "value": target.binds[owner_id][id]})
|
||||
var assigned: Variant = 0
|
||||
var physical: Variant = 0
|
||||
var fractional: bool = false
|
||||
for slot : Dictionary in slots:
|
||||
slot.whole = E.floor_value(slot.value)
|
||||
slot.rest = E.subtract(slot.value, slot.whole)
|
||||
assigned = E.add(assigned, slot.whole)
|
||||
physical = E.add(physical, E.maximum(0, slot.value))
|
||||
fractional = fractional or E.ne(slot.rest, 0)
|
||||
if not fractional: continue
|
||||
slots.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.owner), str(b.owner))))
|
||||
var left: Variant = E.subtract(E.round_value(physical[id]), assigned)
|
||||
var left: Variant = E.subtract(E.round_value(physical), assigned)
|
||||
for slot : Dictionary in slots:
|
||||
if E.gt(left, 0):
|
||||
slot.whole = E.add(slot.whole, 1)
|
||||
@@ -182,15 +193,21 @@ func bind_new_owners(target: Dictionary, amounts: Dictionary, require_full: bool
|
||||
if require_full:
|
||||
return bind_full(target, amounts)
|
||||
var requests: Dictionary = {}
|
||||
var distributions: Dictionary = {}
|
||||
for owner_id in amounts:
|
||||
if (not world.catalog.entities.has(owner_id)):
|
||||
continue
|
||||
if E.le(amounts[owner_id], 0): continue
|
||||
for id in world.catalog.entities[owner_id].bind:
|
||||
var quantity: Variant = E.multiply(amounts[owner_id], E.maximum(0, world.catalog.entities[owner_id].bind[id]))
|
||||
var weights: Dictionary = {}
|
||||
for member in world.catalog.members(id):
|
||||
weights[member] = target.amount.get(member, 0)
|
||||
weights = QuantityMath.normalize_distribution(weights)
|
||||
# Requests are collected before any stock is changed. Every owner
|
||||
# asking for this resource therefore uses the same distribution.
|
||||
if not distributions.has(id):
|
||||
var members: Dictionary = {}
|
||||
for member in world.catalog.members(id):
|
||||
members[member] = target.amount.get(member, 0)
|
||||
distributions[id] = QuantityMath.normalize_distribution(members)
|
||||
var weights: Dictionary = distributions[id]
|
||||
for member in weights:
|
||||
if (not requests.has(member)):
|
||||
requests[member] = {}
|
||||
@@ -256,17 +273,23 @@ func process_entity(entity_id: String, amount: Variant) -> void:
|
||||
## Freeze holdings before simultaneous transitions and reserve successors.
|
||||
func transfer_evolution(target: Dictionary, fractions: Dictionary, outputs: Dictionary) -> Dictionary:
|
||||
# Use a frozen binding table so simultaneous transitions cannot cascade by type order.
|
||||
var bindings: Dictionary = target.binds.duplicate(true)
|
||||
var bindings: Dictionary = target.binds.duplicate()
|
||||
var incoming: Dictionary = {}
|
||||
for owner_id in bindings:
|
||||
var owner_survival: Variant = E.subtract(1, fractions.get(owner_id, 0))
|
||||
var stellar_heirs: Dictionary = world.stellar.stellar_successors(outputs.get(owner_id, {}))
|
||||
target.binds[owner_id] = {}
|
||||
for id in bindings[owner_id]:
|
||||
var held: Variant = bindings[owner_id][id]
|
||||
var fraction: Variant = fractions.get(id, 0)
|
||||
if E.eq(held, 0) or (E.eq(owner_survival, 1) and E.eq(fraction, 0)):
|
||||
# Preserve the holding without running release/inheritance math
|
||||
# for populations that did not participate in this generation.
|
||||
target.binds[owner_id][id] = held
|
||||
continue
|
||||
var released: Variant = E.multiply(held, (E.subtract(1, owner_survival)))
|
||||
target.amount[id] = E.add(target.amount.get(id, 0), released)
|
||||
var heirs: Dictionary = (world.stellar.stellar_successors(outputs.get(owner_id, {})) if (world.catalog.entities.has(id) and E.eq(world.catalog.entities[id].group_as, "Planet")) else {})
|
||||
var fraction: Variant = fractions.get(id, 0)
|
||||
var heirs: Dictionary = (stellar_heirs if (world.catalog.entities.has(id) and E.eq(world.catalog.entities[id].group_as, "Planet")) else {})
|
||||
for heir in heirs:
|
||||
if (not incoming.has(heir)):
|
||||
incoming[heir] = {}
|
||||
|
||||
@@ -65,6 +65,27 @@ var _requested_speed: int = 0
|
||||
var _queued_productions: Array = []
|
||||
var frame_active_clock: Dictionary = {}
|
||||
var _resume_clock: Dictionary = {}
|
||||
var frame_step_seconds: float = 0.1
|
||||
var _frame_step_speed: int = 0
|
||||
var _frame_started_usec: int = 0
|
||||
var _frame_duration_seconds: float = 0.0
|
||||
var _frame_cost_ratio: float = 0.0
|
||||
|
||||
## Hysteresis prevents alternating step sizes around one queue threshold.
|
||||
## Only scheduling uses float; both clocks and the accumulated debt stay exact.
|
||||
func update_frame_step() -> float:
|
||||
if _frame_step_speed != speed:
|
||||
_frame_step_speed = speed
|
||||
frame_step_seconds = 0.1
|
||||
_frame_cost_ratio = 0.0
|
||||
if speed < 4: return 1.0 / 60.0
|
||||
var debt: Variant = E.maximum(0, E.subtract(frame_clock.get("datetime", state.time.datetime), state.time.datetime))
|
||||
var seconds: float = E.approx(E.divide(debt, state.speed_in_year))
|
||||
if seconds > frame_step_seconds * 2.0 or (_frame_cost_ratio > 0.85 and seconds > frame_step_seconds):
|
||||
frame_step_seconds = 0.2 if frame_step_seconds < 0.2 else 0.5
|
||||
elif seconds < frame_step_seconds * 0.5:
|
||||
frame_step_seconds = 0.2 if frame_step_seconds > 0.2 else 0.1
|
||||
return frame_step_seconds
|
||||
|
||||
## Keep the requested clock advancing while a transaction spans render frames.
|
||||
func evolute_frame(delta: float, tree: SceneTree) -> void:
|
||||
@@ -76,14 +97,14 @@ func evolute_frame(delta: float, tree: SceneTree) -> void:
|
||||
frame_busy = true
|
||||
frame_budget = preload("res://world/owniverse/frame_budget.gd").new(tree)
|
||||
frame_active_clock = _resume_clock if not _resume_clock.is_empty() else frame_clock.duplicate()
|
||||
# Keep each transaction at the normal 60 Hz simulation interval. The
|
||||
# requested clock holds the backlog, rather than feeding an ever-growing
|
||||
# multi-generation interval into one transaction and freezing publication.
|
||||
# Bound catch-up work; never turn the entire accumulated debt into one step.
|
||||
if _resume_clock.is_empty():
|
||||
var normal_clock: Dictionary = SimulationTime.advance(state.time, 1.0 / 60.0, speed)
|
||||
var normal_clock: Dictionary = SimulationTime.advance(state.time, update_frame_step(), speed)
|
||||
if E.lt(normal_clock.datetime, frame_clock.datetime):
|
||||
frame_active_clock = normal_clock
|
||||
_resume_clock = {}
|
||||
_frame_duration_seconds = E.approx(E.divide(E.subtract(frame_active_clock.datetime, state.time.datetime), state.speed_in_year))
|
||||
_frame_started_usec = Time.get_ticks_usec()
|
||||
evolution.run_frame(frame_active_clock, frame_budget)
|
||||
|
||||
func request_production(id: String, amount: Variant, resource: bool = false) -> void:
|
||||
@@ -108,6 +129,9 @@ func restore_frame_checkpoint(checkpoint: Dictionary) -> void:
|
||||
_requested_speed = checkpoint.get("speed", state.speed)
|
||||
|
||||
func finish_frame() -> void:
|
||||
if _frame_started_usec > 0 and _frame_duration_seconds > 0:
|
||||
var ratio: float = (Time.get_ticks_usec() - _frame_started_usec) / 1000000.0 / _frame_duration_seconds
|
||||
_frame_cost_ratio = ratio if _frame_cost_ratio == 0.0 else lerpf(_frame_cost_ratio, ratio, 0.25)
|
||||
frame_busy = false
|
||||
frame_commits += 1
|
||||
if _requested_speed != 0:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
extends Node
|
||||
class_name OwniverseEntity
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const QuantityMath = preload("res://world/owniverse/quantity_math.gd")
|
||||
|
||||
var id: String
|
||||
var is_visible: bool = false
|
||||
@@ -10,6 +11,8 @@ var bind: Dictionary = {}
|
||||
var mass: Variant
|
||||
var space: int
|
||||
var life_rule: Dictionary = {}
|
||||
var life_fall_weights: Dictionary = {}
|
||||
var generation_plan: Dictionary = {}
|
||||
var lifespan: Variant
|
||||
var lifespan_delta: Variant
|
||||
var auto: Variant
|
||||
@@ -47,6 +50,8 @@ func _init(values: Dictionary) -> void:
|
||||
life_rule = Global.LifeRules.get(id, {}).duplicate(true)
|
||||
if Global.CivilizationRules.has(id):
|
||||
life_rule = {"ethical": true, "span": Global.CivilizationRules[id].span}
|
||||
elif not life_rule.is_empty():
|
||||
life_fall_weights = QuantityMath.rule_weights(str(life_rule.get("fall", "")))
|
||||
lifespan = (E.of(life_rule.span) if (not life_rule.is_empty()) else E.of(values.lifespan))
|
||||
lifespan_delta = (E.divide(lifespan, Global.batch_count) if E.gt(lifespan, 10) else lifespan)
|
||||
auto = E.of(values.auto)
|
||||
@@ -72,6 +77,40 @@ func _init(values: Dictionary) -> void:
|
||||
space_bonus = E.of(values.space_bonus)
|
||||
civ_bonus = E.of(values.civ_bonus)
|
||||
claim = values.claim
|
||||
refresh_generation_plan()
|
||||
|
||||
|
||||
func refresh_generation_plan() -> void:
|
||||
var fall_text: String = str(life_rule.get("fall", ""))
|
||||
if not generation_plan.is_empty() \
|
||||
and E.eq(generation_plan.outputs, output_and) \
|
||||
and E.eq(generation_plan.chance, output_or.get("chance", 0)) \
|
||||
and E.eq(generation_plan.positive, output_or.get("positive", "")) \
|
||||
and E.eq(generation_plan.negative, output_or.get("negative", "")) \
|
||||
and E.eq(generation_plan.blast, blast) \
|
||||
and E.eq(generation_plan.life_progress, life_rule.get("progress", 0)) \
|
||||
and E.eq(generation_plan.life_degradation, life_rule.get("degradation", 0)) \
|
||||
and E.eq(generation_plan.life_next, str(life_rule.get("next", id))) \
|
||||
and E.eq(generation_plan.life_fall_text, fall_text):
|
||||
return
|
||||
life_fall_weights = QuantityMath.rule_weights(fall_text) if not fall_text.is_empty() else {}
|
||||
generation_plan = {
|
||||
"life": not life_rule.is_empty(),
|
||||
"life_progress": E.of(life_rule.get("progress", 0)),
|
||||
"life_degradation": E.of(life_rule.get("degradation", 0)),
|
||||
"life_next": str(life_rule.get("next", id)),
|
||||
"life_fall": life_fall_weights,
|
||||
"outputs": output_and,
|
||||
"chance": output_or.get("chance", 0),
|
||||
"positive": output_or.get("positive", ""),
|
||||
"negative": output_or.get("negative", ""),
|
||||
"life_fall_text": fall_text,
|
||||
"positive_terms": QuantityMath.output_terms(str(output_or.get("positive", ""))),
|
||||
"negative_terms": QuantityMath.output_terms(str(output_or.get("negative", ""))),
|
||||
"blast": blast,
|
||||
"destroy_level": destroy_level
|
||||
}
|
||||
generation_plan.make_read_only()
|
||||
|
||||
|
||||
func _parse_or_string(source_line: String) -> Dictionary:
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
extends RefCounted
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
## Quantity arithmetic without scene tree or global settings.
|
||||
static var _weight_rules: Dictionary = {}
|
||||
static var _output_rules: Dictionary = {}
|
||||
|
||||
## Parsed rules are shared read-only; the string itself is the cache key.
|
||||
static func rule_weights(text: String) -> Dictionary:
|
||||
if _weight_rules.has(text): return _weight_rules[text]
|
||||
var result: Dictionary = {}
|
||||
for term in text.split("^", false):
|
||||
var pair: PackedStringArray = term.split(":")
|
||||
result[pair[0]] = E.of(pair[1])
|
||||
if _weight_rules.size() >= 128: _weight_rules.clear()
|
||||
result.make_read_only()
|
||||
_weight_rules[text] = result
|
||||
return result
|
||||
|
||||
static func output_terms(text: String) -> Array:
|
||||
if _output_rules.has(text): return _output_rules[text]
|
||||
var result: Array = []
|
||||
for term in text.split("&"):
|
||||
var pair: PackedStringArray = term.strip_edges().split(":")
|
||||
if pair[0].strip_edges().is_empty(): continue
|
||||
result.append({"id": pair[0].strip_edges(), "amount": E.of(pair[1]) if E.eq(pair.size(), 2) else 1})
|
||||
if _output_rules.size() >= 128: _output_rules.clear()
|
||||
result.make_read_only()
|
||||
_output_rules[text] = result
|
||||
return result
|
||||
|
||||
static func accumulate_whole(target: Dictionary, field: String, key: String, value: Variant) -> Variant:
|
||||
if (not target.has(field)):
|
||||
@@ -17,6 +43,18 @@ static func allocate_whole(requests: Dictionary, limit: Variant) -> Dictionary:
|
||||
for id in requests:
|
||||
result[id] = E.floor_value(E.maximum(0, requests[id]))
|
||||
total = E.add(total, result[id])
|
||||
return _allocate_whole_with_total(requests, limit, result, total)
|
||||
|
||||
|
||||
## Variant for callers that already maintain the sum of floored request values.
|
||||
## The supplied total must equal sum(floor(max(0, requests[id]))).
|
||||
static func allocate_whole_with_total(requests: Dictionary, limit: Variant, request_total: Variant) -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
for id in requests: result[id] = E.floor_value(E.maximum(0, requests[id]))
|
||||
return _allocate_whole_with_total(requests, limit, result, request_total)
|
||||
|
||||
|
||||
static func _allocate_whole_with_total(requests: Dictionary, limit: Variant, result: Dictionary, total: Variant) -> Dictionary:
|
||||
var available: Variant = E.floor_value(E.minimum(E.maximum(0, limit), total))
|
||||
# No apportionment or rounding is necessary when every request fits.
|
||||
if E.eq(available, total): return result
|
||||
@@ -72,34 +110,82 @@ static func normalize_distribution(values: Dictionary) -> Dictionary:
|
||||
return normalized_distribution
|
||||
|
||||
|
||||
static func sum_array(values: Array) -> Variant:
|
||||
var result: Variant = 0
|
||||
for value in values: result = E.add(result, value)
|
||||
return result
|
||||
|
||||
static func sum(pop: Dictionary) -> Variant:
|
||||
var result: Variant = 0
|
||||
for n in pop.values(): result = E.add(result, n)
|
||||
return result
|
||||
|
||||
|
||||
static var _split_keys: Dictionary = {}
|
||||
|
||||
static func split(remainders: Dictionary, key: String, count: Variant, weights: Dictionary) -> Dictionary:
|
||||
if E.le(count, 0): return {}
|
||||
return split_arrays(remainders, key, count, weights.keys(), weights.values())
|
||||
|
||||
static func split_arrays(remainders: Dictionary, key: String, count: Variant, ids: Array, weights: Array, population_factor: int = 0) -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
var sum_weights: Variant = sum(weights)
|
||||
if (E.le(count, 0) or E.le(sum_weights, 0)): return result
|
||||
var ids: Array = weights.keys()
|
||||
if E.le(count, 0): return result
|
||||
var sum_weights: Variant = 0
|
||||
var positive_index: int = -1
|
||||
var positive_count: int = 0
|
||||
for i in weights.size():
|
||||
sum_weights = E.add(sum_weights, weights[i])
|
||||
if E.gt(weights[i], 0):
|
||||
positive_index = i
|
||||
positive_count += 1
|
||||
if E.le(sum_weights, 0): return result
|
||||
var cached: Dictionary = _split_keys.get(key, {})
|
||||
if cached.is_empty() or cached.ids != ids:
|
||||
var keys: Array = []
|
||||
for id in ids: keys.append(key + ":" + str(id))
|
||||
if _split_keys.size() >= 512: _split_keys.clear()
|
||||
cached = {"ids": ids.duplicate(), "keys": keys}
|
||||
_split_keys[key] = cached
|
||||
var remainder_keys: Array = cached.keys
|
||||
# A single branch is exact only when no saved rounding error redistributes it.
|
||||
var single: bool = positive_count == 1 and E.eq(count, E.floor_value(count))
|
||||
if single:
|
||||
for i in ids.size():
|
||||
if E.lt(weights[i], 0) or E.ne(remainders.get(remainder_keys[i], 0), 0):
|
||||
single = false
|
||||
break
|
||||
if single:
|
||||
for i in ids.size():
|
||||
result[ids[i]] = count if i == positive_index else 0
|
||||
remainders[remainder_keys[i]] = 0
|
||||
return result
|
||||
var expected: Array = []
|
||||
var remainder_keys: Array = []
|
||||
if population_factor > 0 and E.eq(sum_weights, E.multiply(count, population_factor)):
|
||||
# Ordinary drift: count cancels exactly from count*weight/(factor*count).
|
||||
expected = E.population_quotas(count, weights, population_factor)
|
||||
else:
|
||||
expected = E.mul_div_many(count, weights, sum_weights)
|
||||
var positives: Array = []
|
||||
var positive_sum: Variant = 0
|
||||
for id in ids:
|
||||
var remainder_key: String = key + ":" + str(id)
|
||||
remainder_keys.append(remainder_key)
|
||||
var value: Variant = E.add(E.mul_div(count, weights[id], sum_weights), remainders.get(remainder_key, 0))
|
||||
expected.append(value)
|
||||
positive_sum = E.add(positive_sum, E.maximum(0, value))
|
||||
var whole_quotas: bool = true
|
||||
for i in ids.size():
|
||||
expected[i] = E.add(expected[i], remainders.get(remainder_keys[i], 0))
|
||||
var positive: Variant = E.maximum(0, expected[i])
|
||||
whole_quotas = whole_quotas and E.eq(positive, E.floor_value(positive))
|
||||
positives.append(positive)
|
||||
positive_sum = E.add(positive_sum, positive)
|
||||
# Fractional identity ratios still round-trip through parts/make in the
|
||||
# original algorithm. Retain them to preserve ties and saved errors exactly.
|
||||
var normalized: Array = positives
|
||||
if E.gt(positive_sum, 0) and (E.ne(positive_sum, count) or not whole_quotas):
|
||||
normalized = E.mul_div_many(count, positives, positive_sum)
|
||||
var order: Array = []
|
||||
var rests: Array = []
|
||||
var assigned: Variant = 0
|
||||
for i in ids.size():
|
||||
var normalized: Variant = E.mul_div(E.maximum(0, expected[i]), count, positive_sum) if E.gt(positive_sum, 0) else 0
|
||||
result[ids[i]] = E.floor_value(normalized)
|
||||
result[ids[i]] = E.floor_value(normalized[i])
|
||||
assigned = E.add(assigned, result[ids[i]])
|
||||
rests.append(E.subtract(normalized, result[ids[i]]))
|
||||
rests.append(E.subtract(normalized[i], result[ids[i]]))
|
||||
order.append(i)
|
||||
order.sort_custom(func(a: int, b: int) -> bool: return E.gt(rests[a], rests[b]) if E.ne(rests[a], rests[b]) else str(ids[a]) < str(ids[b]))
|
||||
for i in order:
|
||||
|
||||
@@ -9,35 +9,38 @@ var direct_completions: bool = true
|
||||
func add_output_terms(outputs: Dictionary, terms: String, count: Variant) -> void:
|
||||
if E.le(count, 0):
|
||||
return
|
||||
for term in terms.split("&"):
|
||||
var parts: PackedStringArray = term.strip_edges().split(":")
|
||||
var id: String = parts[0].strip_edges()
|
||||
var amount: Variant = (E.of(parts[1]) if E.eq(parts.size(), 2) else 1)
|
||||
if (E.ne(id, "") and E.gt(amount, 0)):
|
||||
outputs[id] = E.add(outputs.get(id, 0), E.multiply(count, amount))
|
||||
for term: Dictionary in QuantityMath.output_terms(terms):
|
||||
if E.gt(term.amount, 0):
|
||||
outputs[term.id] = E.add(outputs.get(term.id, 0), E.multiply(count, term.amount))
|
||||
|
||||
|
||||
func evolution_products(target: Dictionary, entity: OwniverseEntity, count: Variant) -> Dictionary:
|
||||
if (not entity.life_rule.is_empty()):
|
||||
return world.life.life_products(target, entity, count)
|
||||
func evolution_products(target: Dictionary, entity: OwniverseEntity, count: Variant, context: Dictionary = {}) -> Dictionary:
|
||||
entity.refresh_generation_plan()
|
||||
var plan: Dictionary = entity.generation_plan
|
||||
if plan.life:
|
||||
return world.life.life_products(target, entity, count, context)
|
||||
var outputs: Dictionary = {}
|
||||
for id in entity.output_and:
|
||||
outputs[id] = E.multiply(count, E.maximum(0, entity.output_and[id]))
|
||||
for id in plan.outputs:
|
||||
outputs[id] = E.multiply(count, E.maximum(0, plan.outputs[id]))
|
||||
if (not entity.output_or.is_empty()):
|
||||
# The parser stores the percentage after '%', e.g. 0.1 means 0.1 percent.
|
||||
var chance: Variant = E.clamp_value(E.multiply(E.divide(entity.output_or.get("chance", 0), 100), WorldParameters.effective(target, "Exp")), 0, 1)
|
||||
var explosiveness: Variant = context.explosiveness if context.has("explosiveness") else WorldParameters.effective(target, "Exp")
|
||||
var chance: Variant = E.clamp_value(E.multiply(E.divide(plan.chance, 100), explosiveness), 0, 1)
|
||||
if (not target.has("evolution_remainders")):
|
||||
target.evolution_remainders = {}
|
||||
var expected: Variant = E.add(E.multiply(count, chance), target.evolution_remainders.get(entity.id, 0))
|
||||
var positive: Variant = E.minimum(count, E.floor_value(expected))
|
||||
target.evolution_remainders[entity.id] = E.maximum(0, E.subtract(expected, positive))
|
||||
add_output_terms(outputs, str(entity.output_or.get("positive", "")), positive)
|
||||
add_output_terms(outputs, str(entity.output_or.get("negative", "")), E.subtract(count, positive))
|
||||
for term: Dictionary in plan.positive_terms:
|
||||
if E.gt(term.amount, 0): outputs[term.id] = E.add(outputs.get(term.id, 0), E.multiply(positive, term.amount))
|
||||
for term: Dictionary in plan.negative_terms:
|
||||
if E.gt(term.amount, 0): outputs[term.id] = E.add(outputs.get(term.id, 0), E.multiply(E.subtract(count, positive), term.amount))
|
||||
# Entity.blast is authoritative even if imported output still contains
|
||||
# blast:N. Zero disables the event; never add both representations.
|
||||
outputs.erase("blast")
|
||||
if E.gt(entity.blast, 0):
|
||||
outputs["blast"] = E.multiply(E.multiply(count, entity.blast), WorldParameters.effective(target, "Exp"))
|
||||
if E.gt(plan.blast, 0):
|
||||
var explosiveness: Variant = context.explosiveness if context.has("explosiveness") else WorldParameters.effective(target, "Exp")
|
||||
outputs["blast"] = E.multiply(E.multiply(count, plan.blast), explosiveness)
|
||||
return outputs
|
||||
|
||||
|
||||
@@ -65,11 +68,12 @@ func stellar_successors(products: Dictionary) -> Dictionary:
|
||||
return QuantityMath.normalize_distribution(weights)
|
||||
|
||||
|
||||
func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level: int, cause: String = "local") -> void:
|
||||
func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level: int, cause: String = "local") -> bool:
|
||||
if (E.le(ratio, 0) or not E.contains([1, 2], level)):
|
||||
return
|
||||
return false
|
||||
var held: Dictionary = target.binds.get(owner_id, {})
|
||||
var rocky: Variant = 0
|
||||
var changed: bool = false
|
||||
for id in held.keys():
|
||||
if ((not world.catalog.entities.has(id)) or E.ne(world.catalog.entities[id].group_as, "Planet")):
|
||||
continue
|
||||
@@ -81,6 +85,7 @@ func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level
|
||||
target.destruction_remainders[key] = 0
|
||||
if E.le(destroyed, 0):
|
||||
continue
|
||||
changed = true
|
||||
var physical: Variant = world.ledger.physical_amount(target, id)
|
||||
held[id] = E.subtract(held[id], destroyed)
|
||||
if E.gt(physical, 0):
|
||||
@@ -91,6 +96,7 @@ func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level
|
||||
rocky = world.ledger.credit(target, "RP", rocky)
|
||||
target.amount.RP = E.subtract(target.amount.RP, rocky)
|
||||
held.RP = E.add(held.get("RP", 0), rocky)
|
||||
return changed
|
||||
|
||||
|
||||
func apply_blast(target: Dictionary, power: Variant, environment: Dictionary) -> void:
|
||||
|
||||
@@ -16,24 +16,24 @@ func stellar_environment(source: Dictionary) -> Dictionary:
|
||||
else:
|
||||
mass = E.add(mass, E.multiply(quantities[id], E.maximum(0, entity.mass)))
|
||||
volume += E.approx(quantities[id]) * maxf(0.0, E.approx(entity.space))
|
||||
volume += E.approx(quantities[id]) * maxf(0.0, E.approx(entity.voids)) * 1e22
|
||||
volume += E.approx(quantities[id]) * maxf(0.0, E.approx(entity.voids)) * E.approx(Global.ExtraParams.VoidSize)
|
||||
var metallicity: Variant = 0
|
||||
if E.gt(mass, 0):
|
||||
metallicity = E.maximum(0, E.add(E.multiply(E.of("0.4"), E.logarithm(E.add(E.mul_div(100, source.amount.get("Met", 0), mass), E.of("0.11")))), E.of("0.88")))
|
||||
var base_mass: Variant = E.multiply(65536, ((E.power(mass, E.of("0.1")) if (E.gt(mass, 0) and E.lt(mass, E.of("1.0e10"))) else ((10 if E.ge(mass, E.of("1.0e10")) else 1)))))
|
||||
var base_mass: Variant = E.multiply(Global.ExtraParams.DA, ((E.power(mass, E.of("0.1")) if (E.gt(mass, 0) and E.lt(mass, E.of("1.0e10"))) else ((10 if E.ge(mass, E.of("1.0e10")) else 1)))))
|
||||
if not is_finite(volume) or not is_finite(E.approx(E.add(base_mass, mass))):
|
||||
# Dynamic counts may exceed the float exponent range. Evaluate the
|
||||
# same geometry using logarithms, avoiding infinity / infinity.
|
||||
var exact_volume: Variant = quantities.get("S", 0)
|
||||
for id in quantities:
|
||||
var entity: OwniverseEntity = world.catalog.entities[id]
|
||||
var size: Variant = E.add(E.maximum(0, entity.space), E.multiply(E.maximum(0, entity.voids), E.of("1e22")))
|
||||
var size: Variant = E.add(E.maximum(0, entity.space), E.multiply(E.maximum(0, entity.voids), Global.ExtraParams.VoidSize))
|
||||
exact_volume = E.add(exact_volume, E.multiply(quantities[id], size))
|
||||
var density_log: float = log(1.23456e-7) + E.approx(E.logarithm(E.add(base_mass, mass))) / 2.0 - E.approx(E.logarithm(E.maximum(1, exact_volume))) / 6.0
|
||||
var density_log: float = log(E.approx(Global.ExtraParams.NPC)) + E.approx(E.logarithm(E.add(base_mass, mass))) / 2.0 - E.approx(E.logarithm(E.maximum(1, exact_volume))) / 6.0
|
||||
var density: Variant = E.of("1e%d" % int(floor(density_log / log(10.0))))
|
||||
density = E.multiply(density, E.coefficient(exp(density_log - floor(density_log / log(10.0)) * log(10.0))))
|
||||
return {"metallicity": metallicity, "density": density, "blast": E.clamp_value(E.divide(source.amount.get("S", 0), exact_volume), 0, 1) if E.gt(exact_volume, 0) else 1}
|
||||
return {"metallicity": metallicity, "density": E.coefficient(1.23456e-7 * sqrt(E.approx(E.add(base_mass, mass))) / pow(maxf(1.0, volume), 1.0 / 6.0)), "blast": E.coefficient(clampf(E.approx(source.amount.get("S", 0)) / volume, 0.0, 1.0)) if volume > 0.0 else 1}
|
||||
return {"metallicity": metallicity, "density": E.coefficient(E.approx(Global.ExtraParams.NPC) * sqrt(E.approx(E.add(base_mass, mass))) / pow(maxf(1.0, volume), 1.0 / 6.0)), "blast": E.coefficient(clampf(E.approx(source.amount.get("S", 0)) / volume, 0.0, 1.0)) if volume > 0.0 else 1}
|
||||
|
||||
|
||||
func modify_world_parameter(key: String, change: Variant) -> void:
|
||||
@@ -51,3 +51,4 @@ func modify_world_parameter(key: String, change: Variant) -> void:
|
||||
# withdrawal carry remain intact; already elapsed time is not replayed.
|
||||
for field in ["start_year", "end_year", "processed_until"]:
|
||||
if batch.has(field): batch[field] = E.add(now, E.multiply(E.maximum(0, E.subtract(batch[field], now)), ratio))
|
||||
world.presentation.refresh(world.state, world.catalog)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
extends RefCounted
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
static var INITIAL_ENERGY = E.of("1.0e27")
|
||||
static func extra_params() -> Dictionary:
|
||||
return Engine.get_main_loop().root.get_node("Global").ExtraParams
|
||||
|
||||
static var INITIAL_ENERGY: Variant:
|
||||
get: return extra_params().E
|
||||
const TRENDS = ["Antitheism", "Integration", "Xenocide", "Reconstruction", "Spirituality"]
|
||||
const WEIGHTS = {"m": [0, 2, 2, 0, 6], "l": [6, 1, 1, 2, 0], "w": [2, 0, 6, 0, 2], "d": [0, 6, 0, 1, 3], "c": [1, 3, 3, 3, 0], "b": [3, 0, 0, 6, 1]}
|
||||
|
||||
@@ -26,25 +30,25 @@ static func production(source: Dictionary, entity) -> Variant:
|
||||
return effective(source, "Exp")
|
||||
if E.eq(entity.group_as, "Star"):
|
||||
var burn: Variant = effective(source, "Bur")
|
||||
return (E.square_root(burn) if E.gt(burn, 1) else burn)
|
||||
return (E.power(burn, extra_params().combustibility_bonus) if E.gt(burn, 1) else burn)
|
||||
return 1
|
||||
|
||||
static func density_display(density: Variant) -> Dictionary:
|
||||
if E.le(density, E.of("1.0e-5")):
|
||||
if E.le(density, extra_params().bigrip1):
|
||||
# Unity had log10(density) - 7 here; +7 maps 1e-7..1e-5 to 0..1.
|
||||
return {"mode": "DensityRip", "position": E.clamp_value(E.divide((E.add(E.divide(E.logarithm(E.maximum(density, E.of("1e-7"))), E.logarithm(10)), 7)), 2), 0, 1)}
|
||||
if E.ge(density, E.of("0.9")):
|
||||
return {"mode": "DensityCrunch", "position": E.clamp_value(E.multiply((E.subtract(density, E.of("0.9"))), 10), 0, 1)}
|
||||
return {"mode": "Density", "position": E.clamp_value(E.divide(E.logarithm(E.multiply(density, 100000)), E.logarithm(90000)), 0, 1)}
|
||||
return {"mode": "DensityRip", "position": E.clamp_value(E.divide(E.logarithm(E.divide(E.maximum(density, extra_params().bigrip3), extra_params().bigrip3)), E.logarithm(E.divide(extra_params().bigrip1, extra_params().bigrip3))), 0, 1)}
|
||||
if E.ge(density, extra_params().bigcrunch1):
|
||||
return {"mode": "DensityCrunch", "position": E.clamp_value(E.divide(E.subtract(density, extra_params().bigcrunch1), E.subtract(extra_params().bigcrunch3, extra_params().bigcrunch1)), 0, 1)}
|
||||
return {"mode": "Density", "position": E.clamp_value(E.divide(E.logarithm(E.divide(density, extra_params().bigrip1)), E.logarithm(E.divide(extra_params().bigcrunch1, extra_params().bigrip1))), 0, 1)}
|
||||
|
||||
## Unity ApocalypseCheck thresholds and WarningUI colors; visual warning only.
|
||||
static func density_warning_color(density: Variant) -> Color:
|
||||
if E.ge(density, 1): return Color.html("00c0ffff")
|
||||
if E.ge(density, E.of("0.97")): return Color.html("20b0dfbf")
|
||||
if E.ge(density, E.of("0.9")): return Color.html("40a0c07f")
|
||||
if E.le(density, E.of("1.0e-7")): return Color.html("ff0000ff")
|
||||
if E.le(density, E.of("1.0e-6")): return Color.html("df2020bf")
|
||||
if E.le(density, E.of("1.0e-5")): return Color.html("c040407f")
|
||||
if E.ge(density, extra_params().bigcrunch3): return Color.html("00c0ffff")
|
||||
if E.ge(density, extra_params().bigcrunch2): return Color.html("20b0dfbf")
|
||||
if E.ge(density, extra_params().bigcrunch1): return Color.html("40a0c07f")
|
||||
if E.le(density, extra_params().bigrip3): return Color.html("ff0000ff")
|
||||
if E.le(density, extra_params().bigrip2): return Color.html("df2020bf")
|
||||
if E.le(density, extra_params().bigrip1): return Color.html("c040407f")
|
||||
return Color(0, 0, 0, 0)
|
||||
|
||||
## Integrate the population present at the start of the simulation interval.
|
||||
|
||||
Reference in New Issue
Block a user