diff --git a/autoload/save_manager.gd b/autoload/save_manager.gd index 9c7f7bb..2c6fb55 100644 --- a/autoload/save_manager.gd +++ b/autoload/save_manager.gd @@ -6,7 +6,7 @@ const E = preload("res://world/owniverse/math/exact.gd") const SETTING_SECTION = "Settings" const SAVE_PATH = "user://settings.cfg" -const FORMAT_VERSION : int = 3 +const FORMAT_VERSION : int = 4 const SimulationTime = preload("res://world/owniverse/simulation_time.gd") const WorldParameters = preload("res://world/owniverse/world_parameters.gd") const STATE_VERSION : String = SimulationTime.STATE_VERSION @@ -179,7 +179,7 @@ func load_data(filename: String, password: String) -> Dictionary: static func _number(value: Variant, whole: bool = false, nonnegative: bool = true) -> bool: - return E.numeric(value) and E.finite(value) and (not whole or E.eq(value, E.floor_value(value))) and (not nonnegative or E.ge(value, 0)) + return E.numeric(value) and E.finite(value) and (not value is float or absf(value) < 1e28) and (not whole or E.eq(value, E.floor_value(value))) and (not nonnegative or E.ge(value, 0)) static func _number_map(value: Variant, whole: bool = false, nonnegative: bool = true) -> bool: @@ -249,11 +249,8 @@ static func _validate_payload(data: Variant, speed_limit: int, stages: Array) -> return "unsupported state.version" if (((not (saved.time is Dictionary)) or (not _number(saved.time.get("datetime")))) or (not _number(saved.time.get("year")))): return "invalid state.time" - if ((not E.finite(E.multiply(E.of(saved.time.datetime), 10))) or E.ne(saved.time.year, SimulationTime.processed_year(saved.time.datetime))): + if E.ne(saved.time.year, SimulationTime.processed_year(saved.time.datetime)): return "invalid processed time" - if saved.time.has("ticks"): - if not _number(saved.time.ticks, true) or not E.eq(saved.time.ticks, E.multiply(saved.time.datetime, SimulationTime.TICKS_PER_YEAR)): - return "invalid time ticks" if saved.time.has("tick_remainder"): if not E.finite(saved.time.tick_remainder) or E.ge(E.absolute(saved.time.tick_remainder), 1): return "invalid fractional tick" diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..ebb5681 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,15 @@ +[gd_resource type="AudioBusLayout" format=3 uid="uid://b5g7p85k5do0m"] + +[resource] +bus/1/name = &"Music" +bus/1/solo = false +bus/1/mute = false +bus/1/bypass_fx = false +bus/1/volume_db = 0.0 +bus/1/send = &"Master" +bus/2/name = &"FX" +bus/2/solo = false +bus/2/mute = false +bus/2/bypass_fx = false +bus/2/volume_db = 0.0 +bus/2/send = &"Master" diff --git a/simulation_rules.md b/simulation_rules.md index 823197f..d44e501 100644 --- a/simulation_rules.md +++ b/simulation_rules.md @@ -1,9 +1,10 @@ # Эволюция звёзд Owniverse.GI -Числа симуляции переведены на целочисленное хранение с большими разрядами и -рациональную арифметику. Количества до `10^27` сохраняют единицы; время хранится -в тиках по `10^-9` года. Границы точности коэффициентов, ограничение накопленных -дробей и несовместимый со старым формат сохранения 3 описаны в +Числа симуляции хранят целую часть в двух блоках int64, дробный остаток — в +float. Весь порядок E27 сохраняет целые единицы. Возраст разделён на целые +годы и дробную часть с шагом 10^-9 года; большие промежуточные произведения +распределений вычисляются через `mul_div`. Границы точности, допуск переноса +дробей и несовместимый со старым формат сохранения 4 описаны в [документации математики](world/owniverse/math/README.md). Механики перенесены из Owniverse.U с согласованными изменениями GI: постепенные партии, детерминированные дробные остатки, уязвимость цивилизаций до C1.2 и передача выживших планет преемнику. Баланс таблицы GI сохраняется. diff --git a/sound/effects/black_hole_feed.ogg b/sound/effects/black_hole_feed.ogg new file mode 100644 index 0000000..130bce4 Binary files /dev/null and b/sound/effects/black_hole_feed.ogg differ diff --git a/sound/effects/blast.ogg b/sound/effects/blast.ogg new file mode 100644 index 0000000..a59f178 Binary files /dev/null and b/sound/effects/blast.ogg differ diff --git a/sound/effects/civ_birth.ogg b/sound/effects/civ_birth.ogg new file mode 100644 index 0000000..a6dae63 Binary files /dev/null and b/sound/effects/civ_birth.ogg differ diff --git a/sound/effects/civ_death.ogg b/sound/effects/civ_death.ogg new file mode 100644 index 0000000..79d6e75 Binary files /dev/null and b/sound/effects/civ_death.ogg differ diff --git a/sound/effects/civ_top.ogg b/sound/effects/civ_top.ogg new file mode 100644 index 0000000..618c5ce Binary files /dev/null and b/sound/effects/civ_top.ogg differ diff --git a/sound/effects/cloud_decay.ogg b/sound/effects/cloud_decay.ogg new file mode 100644 index 0000000..c0842c5 Binary files /dev/null and b/sound/effects/cloud_decay.ogg differ diff --git a/sound/effects/create.ogg b/sound/effects/create.ogg new file mode 100644 index 0000000..42bbfa8 Binary files /dev/null and b/sound/effects/create.ogg differ diff --git a/sound/effects/force.ogg b/sound/effects/force.ogg new file mode 100644 index 0000000..b094827 Binary files /dev/null and b/sound/effects/force.ogg differ diff --git a/sound/effects/gamma_ray.ogg b/sound/effects/gamma_ray.ogg new file mode 100644 index 0000000..510ac84 Binary files /dev/null and b/sound/effects/gamma_ray.ogg differ diff --git a/sound/effects/life_birth.ogg b/sound/effects/life_birth.ogg new file mode 100644 index 0000000..41f970f Binary files /dev/null and b/sound/effects/life_birth.ogg differ diff --git a/sound/effects/life_death.ogg b/sound/effects/life_death.ogg new file mode 100644 index 0000000..332c749 Binary files /dev/null and b/sound/effects/life_death.ogg differ diff --git a/sound/effects/select.ogg b/sound/effects/select.ogg new file mode 100644 index 0000000..4141dbf Binary files /dev/null and b/sound/effects/select.ogg differ diff --git a/sound/effects/star_birth.ogg b/sound/effects/star_birth.ogg new file mode 100644 index 0000000..fae5c50 Binary files /dev/null and b/sound/effects/star_birth.ogg differ diff --git a/sound/effects/structure_decay.ogg b/sound/effects/structure_decay.ogg new file mode 100644 index 0000000..35fe6e6 Binary files /dev/null and b/sound/effects/structure_decay.ogg differ diff --git a/sound/effects/swipe.ogg b/sound/effects/swipe.ogg new file mode 100644 index 0000000..f02b053 Binary files /dev/null and b/sound/effects/swipe.ogg differ diff --git a/sound/effects/time_minus.ogg b/sound/effects/time_minus.ogg new file mode 100644 index 0000000..96910b5 Binary files /dev/null and b/sound/effects/time_minus.ogg differ diff --git a/sound/effects/time_plus.ogg b/sound/effects/time_plus.ogg new file mode 100644 index 0000000..7bac35d Binary files /dev/null and b/sound/effects/time_plus.ogg differ diff --git a/sound/effects/trend_action.ogg b/sound/effects/trend_action.ogg new file mode 100644 index 0000000..ccd60cf Binary files /dev/null and b/sound/effects/trend_action.ogg differ diff --git a/sound/effects/window_close.ogg b/sound/effects/window_close.ogg new file mode 100644 index 0000000..8e76910 Binary files /dev/null and b/sound/effects/window_close.ogg differ diff --git a/sound/effects/window_open.ogg b/sound/effects/window_open.ogg new file mode 100644 index 0000000..a22ff8a Binary files /dev/null and b/sound/effects/window_open.ogg differ diff --git a/sound/music/apocalypse_the_end.ogg b/sound/music/apocalypse_the_end.ogg new file mode 100644 index 0000000..1887fb3 Binary files /dev/null and b/sound/music/apocalypse_the_end.ogg differ diff --git a/sound/music/cosmic_unicorns.ogg b/sound/music/cosmic_unicorns.ogg new file mode 100644 index 0000000..0dcf7b6 Binary files /dev/null and b/sound/music/cosmic_unicorns.ogg differ diff --git a/sound/music/funky_eldritch.ogg b/sound/music/funky_eldritch.ogg new file mode 100644 index 0000000..de041f7 Binary files /dev/null and b/sound/music/funky_eldritch.ogg differ diff --git a/sound/music/meditative_beings.ogg b/sound/music/meditative_beings.ogg new file mode 100644 index 0000000..e6fef5e Binary files /dev/null and b/sound/music/meditative_beings.ogg differ diff --git a/sound/music/owniverse_main_theme.ogg b/sound/music/owniverse_main_theme.ogg new file mode 100644 index 0000000..0b2f4c2 Binary files /dev/null and b/sound/music/owniverse_main_theme.ogg differ diff --git a/tests/README.md b/tests/README.md index 1d2e8d2..2d1899f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,24 +6,25 @@ godot --headless --path Owniverse.GI --script res://tests/benchmark_saved_state.gd -- /tmp/save.adventure.json ``` -Читает диагностический JSON формата 3 в отдельные экземпляры мира и замеряет +Читает диагностический JSON формата 4 в отдельные экземпляры мира и замеряет шесть последовательных шагов на скоростях 1, 4, 6. Запись сохранений не вызывается. Последним аргументом можно задать длительность шага в реальных секундах (по умолчанию 1/60). Это время расчёта симуляции, без отрисовки интерфейса. -При проверке регрессии на копии сохранения с 120 партиями шаг на скорости 6 -занимал около 1,3 секунды. Главная причина — перебор пустых завершённых окон -коротких взрывов; дополнительная — повторное сокращение уже сокращённых дробей. -После пропуска таких окон и устранения повторных НОД замеры составили 90–99 мс -на шаг 1/60 секунды. Это ускорение примерно в 14 раз, но ещё не бюджет 60 FPS. -Размер партии, точность чисел и правила эволюции не изменены. Разреженные -сценарии дополнительно сравниваются с полным расчётом в `test_short_lived_events.gd`. +На диагностической копии с 120 партиями прежняя рациональная арифметика после +оптимизации пустых окон давала 90–99 мс на шаг 1/60 секунды на скорости 6. +После перехода на целые блоки и float-остаток повторный замер дал 58–62 мс. +При шаге 0,1 секунды: 71–82 мс вместо 117–138 мс. Это замеры расчёта на данной +копии, а не обещание FPS. Для сравнения старый JSON был преобразован только +во временный тестовый файл; в загрузчик игры миграция не добавлена. +Разреженные сценарии сравниваются с полным расчётом в `test_short_lived_events.gd`. -## Точная арифметика +## Целые блоки и дробные остатки `godot --headless --path Owniverse.GI --script res://tests/test_exact_math.gd` -Независимый эталон Python Fraction, границы int64 и 10^27, точная сериализация, +Независимый эталон Python Fraction: 160 пар с произведением внутри E27; целые +проверяются точно, дроби — с допуском 10^-12. Также границы int64/E27, сериализация, рецепты с дробными стоимостями, сохранение сумм, время и 100 циклов с меняющимися долями. Все остальные тесты используют числовое сравнение и сериализацию через `E`, а не сравнение ссылок на большие числа. Границы точности и новый формат @@ -90,14 +91,14 @@ godot --headless --path Owniverse.GI --script res://tests/test_time.gd Проверяются границы десятых долей года, производство ресурсов на этих границах, накопление дробного остатка, скорости 1–10 000 лет/сек, продолжение после загрузки и недопустимые интервалы. -Схема состояния `0.1.1` хранит: +Схема состояния `0.3.0` хранит: - `time.datetime` — накопленный возраст, включая необработанную дробную часть; - `time.year` — последний рассчитанный возраст с точностью до десятой доли года. -Сохранения используют формат 2 и актуальную структуру состояния. Проверяются целостность данных, фоновая запись и повторная загрузка; поддержка и миграция прежних форматов отсутствуют. +Сохранения используют формат 4 и актуальную структуру состояния. Проверяются целостность данных, фоновая запись и повторная загрузка; поддержка и миграция прежних форматов отсутствуют. -Файл с несовместимым номером формата при загрузке переносится в `*.adventure.incompatible.bak` (при совпадении имени добавляется числовой суффикс). Игра начинает новую сессию и может сохранять её в формате 2. Прогресс несовместимого файла не переносится, исходные байты сохраняются в архиве. Ошибка архивирования, расшифровки или повреждение данных текущего формата по-прежнему блокируют перезапись. +Файл с несовместимым номером формата при загрузке переносится в `*.adventure.incompatible.bak` (при совпадении имени добавляется числовой суффикс). Игра начинает новую сессию и может сохранять её в формате 4. Прогресс несовместимого файла не переносится, исходные байты сохраняются в архиве. Ошибка архивирования, расшифровки или повреждение данных текущего формата по-прежнему блокируют перезапись. ## Согласованные правила эволюции diff --git a/tests/benchmark_saved_state.gd b/tests/benchmark_saved_state.gd index 3f8a850..30e8441 100644 --- a/tests/benchmark_saved_state.gd +++ b/tests/benchmark_saved_state.gd @@ -22,8 +22,8 @@ func run(): quit(1) return var data = JSON.parse_string(FileAccess.get_file_as_string(args[0])) - if not data is Dictionary or data.get("format_version") != 3: - push_error("Expected diagnostic JSON format 3") + if not data is Dictionary or data.get("format_version") != 4: + push_error("Expected diagnostic JSON format 4") quit(1) return var initial: Dictionary = E.decode(restore_json_integers(data)).state diff --git a/tests/fixtures/exact_oracle.json b/tests/fixtures/exact_oracle.json index 8a9d230..8b33f40 100644 --- a/tests/fixtures/exact_oracle.json +++ b/tests/fixtures/exact_oracle.json @@ -1 +1 @@ -[["8965162473773569779732155","452377245931519616402404160","461342408405293186182136315","-443412083457746046622672005","4055635509214296959325273583500651985866982857764800","1793032494754713955946431/90475449186303923280480832","0"],["669722791602174345205316603/5690815","726087406662665745875262985/7117062","8898487735792752316248775717163161/40501883185530","634429525498755983022506269197611/40501883185530","97255456987460741499319009093324434512538016562367991/8100376637106","4766458630645754149635640993180386/4132029105146998166613134723982775","1"],["16937388428525083332123627","496616201568811100067276557/88170741","1493382584964083703291189363474164/88170741","1493381591731680565668989228921050/88170741","2803793835289887153967923369620246934861021707637413/29390247","1493382088347882134480089296197607/496616201568811100067276557","3007115"],["74887937094488977548346049/59817663","557282321277758414756322483","33335326164938619343796902955763278/59817663","-33335326015162745154818947859071180/59817663","13911241139906522802931877657420548336300874107639889/19939221","74887937094488977548346049/33335326090050682249307925407417229","0"],["316737116478662584322058854","289995239575234353789221234/86971001","27546944363998119674970718702514088/86971001","27546943784007640524502011124071620/86971001","91852255975598665059497878256495393976895804174505836/86971001","13773472037001440049868182456646427/144997619787617176894610617","94991021"],["228751740129254785197159100/41995543","231652288763645672060436209/15104359","13183512058609069335863724240333387/634315757871937","-6273215249035127979692170587299587/634315757871937","52990864159608563051869461891686342622840813573851900/634315757871937","3455148404786970678085776826516900/9728363653822098657777947413816487","0"],["-456239815960420554769203829","588849688898442037487042387","132609872938021482717838558","-1045089504858862592256246216","-268656673691376093858826070328372829513616939965699823","-456239815960420554769203829/588849688898442037487042387","-1"],["-420217297218115785214058617/47004324","14802701262412891050577413/1019516","33421588527880002479968115345555/5990207548398","-70263076512768067849771080793949/2995103774199","-2073450372006111861456485088608361893674161026072607/15973886795728","-107104564497656133219574046242343/173947741553416138179510276933453","-1"],["-364115195294134125209756974","48127190763090217292339923/9229439","-3360578935813107203351596904017663/9229439","-3360579032067488729532031488697509/9229439","-17523841463660642425897360330358812322416070627873002/9229439","-3360578983940297966441814196357586/48127190763090217292339923","-69827034"],["-420085018211671896915562717/49163269","845102575105825680064560515","41548004812435393164450029050160818/49163269","-41548005652605429587793822881286252/49163269","-20883231214944799875038828074012089340298129442607015/2891957","-420085018211671896915562717/41548005232520411376121925965723535","-1"],["-75859743557661042427270673","869773624406037795349021071/58328287","-4424768024204029925379225092406080/58328287","-4424769763751278737454815790448222/58328287","-65980804100659420865479763650979591216028484897350783/58328287","-4424768893977654331417020441427151/869773624406037795349021071","-5087265"],["-129734913575946462656699609/32388197","3709952997169215650194524/380705","70767958260126300748264506988883/12330348538885","-169549418805987696879702156277573/12330348538885","-481310431458571743998455455910379733893512684741116/12330348538885","-49390730272930698065718824644345/120158688533056998813983331633228","-1"],["-891749371886510459907209969","935818881198998142563336420","44069509312487682656126451","-1827568253085508602470546389","-834515899508743546222598112102520414011558794124770980","-891749371886510459907209969/935818881198998142563336420","-1"],["309221856785345314773350133/77275973","5400754338136633557913440/450371","556613103275752119892001398526463/34802857235983","-278083989551206610372424453027777/34802857235983","1670031284480118526067328146285400255804782026487520/34802857235983","15473839651363639417754274749927/46372060712608818348023658419680","0"],["-739171440770796651066548101","790330406607280226048572190/94947383","-70182393099196238236652674984997493/94947383","-70182394679857051451213127082141873/94947383","-584189665336872869820803473805288359202118903005911190/94947383","-70182393889526644843932901033569683/790330406607280226048572190","-88801334"],["987967355397369368551343093/8823399","687830972611340633445682485","6069008103875285731173369943809608/8823399","-6069006127940574936434632841123422/8823399","226518182323742202628660017973680839371213769591942035/2941133","987967355397369368551343093/6069007115907930333804001392466515","0"],["996041562962192196582260380","984132128441087109080207381/75195817","74898160077031110774114786061037841/75195817","74898158108766853891940567900623079/75195817","980236503373769283498218952244661589805941676139864780/75195817","74898159092898982333027676980830460/984132128441087109080207381","76105796"],["257428367649261306092744439/49059877","174203248611724385118252189/25216661","15037873828686408347960053925218932/1237126287010697","-2054906071096829835644111769422574/1237126287010697","44844857929314654234318006666840650587911081929326971/1237126287010697","2163827959598263085385990359299393/2848796649963873030600694282440251","0"],["-417920069139760484387061766","700122838708802671649104909","282202769569042187262043143","-1118042907848563156036166675","-292595385159508190511365246175253023082636634396809294","-417920069139760484387061766/700122838708802671649104909","-1"],["-185036012254627576413600829/87970174","800815470679869600444477652/7631389","69035794509076196962671763568729967/671334618191686","-71859958086123857134150589202372929/671334618191686","-74089850623207851000005891527844475735333138669586754/335667309095843","-1412081788523830085739412816821481/70447876297600027048411176385551448","-1"],["-467741399975193346674148870","25091552094286467416814022/1753713","-820284148683144155289494220440288/1753713","-820284198866248343862429054068332/1753713","-11736357704132046836384475906728976239234603931455140/1753713","-410142086887348124787980818627155/12545776047143233708407011","-32691648"],["-889565139924306397473311233/29172277","403944296776753392363570831","11783974028576517198413375517740954/29172277","-11783975807706797047026170464363420/29172277","-359334764883838181180232439438683051732440401303444623/29172277","-889565139924306397473311233/11783974918141657122719772991052187","-1"],["422308681302047471154406358","497821761236496040678030711/43666862","18440895405640248476844483804739307/43666862","18440894409996726003852402448677885/43666862","105117225755623688033788901156943711420198226848830269/21833431","2634413558259783891478349018101228/71117394462356577239718673","37043167"],["-442922889212541300660889303/17771939","762415085060589348906108471/15184903","6823853275204419178779636514202660/269865169836917","-20275335493548791246838516433787878/269865169836917","-337691092254261668282382766867729198935134139841585713/269865169836917","-6725741109172186034029439959792609/13549594384376605212809076473995269","-1"],["-622918721265708898092998296","103366129005906171420524454","-519552592259802726672473842","-726284850271615069513522750","-64388696902545373985577884196115429132384365648330384","-311459360632854449046499148/51683064502953085710262227","-7"],["-493196748910531101879907093/98242015","86249675876988219717280097/22574443","-2660299944813881622826828467548744/2217758767822645","-19606983847318311295403482564899654/2217758767822645","-42538059737117650411930949081842441453474009018028021/2217758767822645","-11133641896066096459115155516224199/8473341951252214836288327048675455","-2"],["-626464823906479176551906151","829288260806078722183008417/94109780","-58956465926289235073802324268248363/94109780","-58956467584865756685959768634265197/94109780","-519519924273590483796101169529968647006246885027072967/94109780","-19652155585192498626627015483752260/276429420268692907394336139","-71092851"],["72603951574110188966251202/3812653","206539448971700890160406466","787463322344253888082933160065500/3812653","-787463177136350739862555227563096/3812653","14995580151284773893991128756629121453638328581072132/3812653","36301975787055094483125601/393731624870151156986372096907149","0"],["-515583821082821814289687301","966054897426495930251835667/70591947","-36396064805881142698285518346929380/70591947","-36396066737990937551277378850600714/70591947","-498082275390926257668491085916331565692978462668764767/70591947","-36396065771936040124781448598765047/966054897426495930251835667","-37674946"],["172268604642639993225760948/48027607","122615808784191111405887877/97943558","22761543946669757961122124947193323/4703994711805706","10983656194121200938131724061952645/4703994711805706","10561427143190681278772278763380713079115860446613698/2351997355902853","16872600070395479449626924504572984/5888943876274278511495200442620339","2"],["-746972200152603017556792964","800890151068266209585888811","53917950915663192029095847","-1547862351220869227142681775","-598242678224013414544565651701722862361515030651125804","-746972200152603017556792964/800890151068266209585888811","-1"],["389567428402925581663480789/47880760","454433278608284359331294111/3249161","23024378044293475506738750722072389/155572298042360","-20492843453819319335052156914336331/155572298042360","177032403728139550467805643869472287822040020157333579/155572298042360","1265767295237078085843296903868029/21758610749056397420895453818204360","0"],["-802677792389070116416466577","201686506557570590774211013/8242102","-6615752036319033027085801233013841/8242102","-6615752439692046142226982781435867/8242102","-161889279838294475272763705900753582011990948459812501/8242102","-6615752238005539584656392007224854/201686506557570590774211013","-32802156"],["-73307779216486676338536582/30793207","290868654896364523822243391","8956778626727537113028095604908355/30793207","-8956778773343095546001448281981519/30793207","-21322935134139146765376014277791685701398169161229562/30793207","-73307779216486676338536582/8956778700035316329514771943444937","-1"],["-625128472204633572303121","64812615847244480554000115/32357319","-20227416578492114532641096892484/32357319","-20227546203723809021602204892714/32357319","-40516211524173764650287314719056456610869848858915/32357319","-20227481391107961777121650892599/64812615847244480554000115","-312092"],["-691328438667808121111963828/48094047","22213213871286297797566909/591498","73267107014618220967855002972931/3160836956934","-164138082306869591639295976380563/3160836956934","-7678313231715226963218343120954825528215774608883826/14223766306203","-45435487646125685335720486703816/118702594660743906303575489676747","-1"],["-603351829131188474626909419","679657102612854146198290236","76305273481665671571380817","-1283008931744042620825199655","-410072356043469406580096998106016016194851054644132884","-201117276377062824875636473/226552367537618048732763412","-1"],["921403334212378866620893639/35497968","263853197052722064316773852/23054323","30608582425884355141462780563684133/818381620115664","11876077734533910837441220440618661/818381620115664","20259601292247827231766190439120561736287133142360619/68198468342972","21242330080209132989452000502151397/9366252345675222152010780061532736","2"],["685342914121562012584908762","331716769340066544501287298/93988433","64414306897655954415248398489637244/93988433","64414306234222415735115309487062648/93988433","2877718194462167350019814681206433823756828314930444/1189727","10735717760989864179196975664724991/55286128223344424083547883","194184655"],["-539315331258854858499286213/95454753","730158412940194204949423750","69697090418762910346623763049297537/95454753","-69697091497393572864333480047869963/95454753","-393785626346280573510277670292116992061821140669758750/95454753","-539315331258854858499286213/69697090958078241605478621548583750","-1"],["352192508098201572070314824","72865657794186793642645149/72629863","25579693685664428520038385676634261/72629863","25579693539933112931664798391343963/72629863","25662738772759916818808382281133524488081140546388776/72629863","25579693612798770725851592033989112/72865657794186793642645149","351052805"],["20810511441917332168475968/8440987","98865997427770415552255255/894088","853133027583924500345032769413869/7546985184856","-815920170475762532977336086859501/7546985184856","257181496335898220472561053412298089313514833651480/943373148107","18606428554080983683848341277184/834526599029843516661184428136685","0"],["-318193078171864637390108107","82113136232409480985521542","-236079941939455156404586565","-400306214404274118375629649","-26127831576136040494713176104679712747624557157340994","-318193078171864637390108107/82113136232409480985521542","-4"],["687178808777085344223419401/93545341","357235553407689853054794871/81719320","89573506632519505238154439417273331/7644461655688120","22738063310827386585653083631781309/7644461655688120","245484702043519164244547493202938066943750914256692271/7644461655688120","56155784971673445911903761524527320/33417721660846059326250677892746011","1"],["887349949653964053570432426","735365804301184159466005146/82116715","72866263656364719108472091418605736/82116715","72866262185633110506103772486595444/82116715","652526809423902546779651258905590309753980917961264196/82116715","12144377153499819134547988658766765/122560967383530693244334191","99088457"],["-847277829422114043463047781/81967757","834804113802525833183907597","68427019895487954058527030758302148/81967757","-68427021590043612902755117684397710/81967757","-707311017535255562633247862045900770559069248699892257/81967757","-847277829422114043463047781/68427020742765783480641074221349929","-1"],["-591378754688379655143141156","235687414896612673253577286/6455359","-3817561930799008905632499296077718/6455359","-3817562402173838698857845803232290/6455359","-139380529917282262917418323143259662186283663853382616/6455359","-1908781083243211901122586274827502/117843707448306336626788643","-16197565"],["40776597925638081985827998/5428493","559883912392724040973629189/363772","3054159283817120923717207859570633/1974733755596","-3024492514655910490996910614593721/1974733755596","11415080590335642376457800591605929095524571593116811/987366877798","14833384580605216360148622488456/3039325899236515707357059237082177","0"],["-668179028263226999164753080","713355211878041953535923045","45176183614814954371169965","-1381534240141268952700676125","-476648992279178478818047829007218718044552167306728600","-133635805652645399832950616/142671042375608390707184609","-1"],["-710943998496919877505660733/87140843","401169925750801982717049551/34345492","10540564103348319022523477686945857/2992895126129756","-59376006928996266377546779006877129/2992895126129756","-285209351089987623662229643167863392604381834555980883/2992895126129756","-24417721412823973677511650659965636/34958285516172292700035128346911493","-1"],["-603482375384047361400049916","712726211253501367870069466/59944893","-36175685707056341732556954539209522/59944893","-36175687132508764239559690279348454/59944893","-430117706965735353380539976040013596226285614787464856/59944893","-18087843209891276493029161204639494/356363105626750683935034733","-50756779"],["794118216034165644737632316/68615327","535839980152899629631603553","36766836251982934119517012061089147/68615327","-36766834663746502051185722585824515/68615327","38683662647163943591657606418811277641966826144838068/6237757","794118216034165644737632316/36766835457864718085351367323456831","0"],["840845974038948185474520460","89972324459124415007625239/16959077","14259971708838847735597089026840659/16959077","14259971528894198817348259011590181/16959077","75652866796380750704874572957284518825890446917889940/16959077","14259971618866523276472674019215420/89972324459124415007625239","158492866"],["1695520524267311439264857/272482","526899858060811215872803991/53215436","116899295534979760255860130904157/7250124216076","-26671431589146201467593246171505/7250124216076","893369523575638616008418051498322436001576295644287/14500248432152","45113931972916779394133442366326/71785363562062980861726688537831","0"],["645460702583830913792912136","909570621640824799650751085","1555031324224655713443663221","-264109919056993885857838949","587092092493898614233333035452575973784139701811667560","645460702583830913792912136/909570621640824799650751085","0"],["-826682468766489450614933110/62308813","903487713050591162489985887/43437394","2912330693080606859976679031858113/386647494279046","-1881717940183053888698888505886479/55235356325578","-373448726562426155939850254947563319961437257674509285/1353266229976661","-5129847444100385180457770254673620/8042178137180992040434449286531733","-1"],["-719310351696520408556688185","697057877514532584878847317/39529085","-28433679336533771919539469704513408/39529085","-28433680730649526948604639462208042/39529085","-100280189405561695772844220537861017321221858758569929/7905817","-28433680033591649434072054583360725/697057877514532584878847317","-40790989"],["-896436610211208874896993882/37394111","566019242191140906976490133","21165785474194796080910671526812881/37394111","-21165787267068016503328421320800645/37394111","-507400370784143613995570125602542023620883009134366306/37394111","-298812203403736291632331294/7055262123543802097373182141268921","-1"],["369534170991056558718552449","854290770538348746180670993/57970492","21422078557454446847090121177005901/57970492","21422076848872905770392628815663915/57970492","315689631676199628285171366163189790437222632483411857/57970492","21422077703163676308741374996334908/854290770538348746180670993","25075862"],["-237711744992069386670726517/86388593","914575146590802056663748292/36595497","70309680656026096152987076811339207/3161433495965721","-87708039557470176680387901292727105/3161433495965721","-72468418007492405330317740092632104068500414685952988/1053811165321907","-8699179450722040263700412240693949/79008860106748136416687489052033156","-1"],["875671974149629151198692521","95366145078415460294360857","971038119228044611493053378","780305829071213690904331664","83509460527856006249256266250323253611804367761050497","875671974149629151198692521/95366145078415460294360857","9"],["-468683394151577789810395027/1928034","829684664474852339774922001/64229227","-28503511871706054813824547183578095/123836133449718","-31702832356478269725955751114130163/123836133449718","-388859424621586789935228929347094731271946877923289027/123836133449718","-30103172114092162269890149148854129/1599660242386107456065601965276034","-19"],["-570514149326162505413735391","769164178649569914889815671/32124781","-18327641335340089407257648938008700/32124781","-18327642873668446706397478717640042/32124781","-438819047074415864792207299117301717960390463559112361/32124781","-18327642104504268056827563827824371/769164178649569914889815671","-23827998"],["-24526382539878620680507485/6012056","89089386314473688646109344","535610355001866886788352877743779/6012056","-535610404054631966545594238758749/6012056","-273130046123976102176809482292562245882775590054980/751507","-8175460846626206893502495/178536793176083142222324519417088","-1"],["-740386922149531335347752287","164954304066661784401048218/65519977","-48510133945383779586102232444889181/65519977","-48510134275292387719425801246985617/65519977","-122130009483233638726166805803250947417876473006774566/65519977","-16170044703446027884254672281979133/54984768022220594800349406","-294082258"],["-436810350208109281457885895/56123534","304110019834046999927049381/16425281","9892996292020207731349887962920959/921844816663054","-24242461783773414584657618945543949/921844816663054","-132838404265505129666391363667099011165067001528380995/921844816663054","-341653940279838258412088832919595/812749001804610055143035878772974","-1"],["913158687511833595601344899","454987440983867598456868456","1368146128495701194058213355","458171246527965997144476443","415475734443196382260944892079585027899997437529605944","913158687511833595601344899/454987440983867598456868456","2"],["-55461815289090552919457529/38727853","775059692568103757914466293/17443737","29048936520557240250947437850213056/675558482306661","-30983859159448189399570636487304802/675558482306661","-4776246389692386732483866454559729988490345235063333/75062053589629","-967461319445474574311599318545873/30016397840002714825259037168758929","-1"],["517396143010315919695964254","701819581444231890181711061/96554604","49956980401308002985371519124836477/96554604","49956978997668840096907738761414355/96554604","181559372264179932010425406370298119820002261006206747/48277302","49956979699488421541139628943125416/701819581444231890181711061","71182083"],["-274878011121965277862286242/80639457","326326813744869216051825845","26314816790048378996469642137079923/80639457","-26314817339804401240400197861652407/80639457","-89700065537957652075762664344425303631999050123524490/80639457","-16169294771880310462487426/1547930415583905301084407058786245","-1"],["148296072827006147456893700","498670987199973354615105205/62373481","9249742779520871425259112131074905/62373481","9249741782178897025312402900864495/62373481","73950949034522298966859088695606049478202628001708500/62373481","1849948456169976845057151503193940/99734197439994670923021041","18548787"],["163294853768806486208524745/31147549","89222407140003514304702063/5991559","3757450049043385743678970750321042/186622377538891","-1800668547539032900276846125066132/186622377538891","14569559926827789612242212143785877451472467988048935/186622377538891","978390750752176421701062312627455/2779059298291209321977908437693587","0"],["-702730176437592873109958020","578313447986569366525053082","-124416728451023506584904938","-1281043624424162239635011102","-406398311339834579836928216914666590765633703291617640","-351365088218796436554979010/289156723993284683262526541","-2"],["-103553791249604278363992887/1834988","114657559817815749529743680/31039519","-3003864624639151654162360836025513/56957144890772","-3424655117389099827358531427777193/56957144890772","-2968306253640776920153418308788334869215833613301040/14239286222693","-3214259871014125740760446131901353/210395246374974086598085295875840","-16"],["-625759471664636727825393167","121004782618619740486879431/9244483","-5784822676887933312937733613768230/9244483","-5784822918897498550177214587527092/9244483","-75719888840321706328089389505859033232645872500247977/9244483","-5784822797892715931557474100647661/121004782618619740486879431","-47806564"],["-169190480203827753383528379/6199906","641466313334409439355360266","3977030675649404885688180861806617/6199906","-3977031014030365293343687628863375/6199906","-54264996793813885539615721459756334361261212889994407/3099953","-169190480203827753383528379/3977030844839885089515934245334996","-1"],["-380418485621919251391274652","413272079029753599430641877/29212087","-11112817485123675248863186744476847/29212087","-11112818311667833308370385605760601/29212087","-157216338454320996229801699602038504856045925559801804/29212087","-11112817898395754278616786175118724/413272079029753599430641877","-26889835"],["-118335243814023190947630885/20898673","52539910302222223419169911/9634619","-42100583564746881374498628179712/201350751960587","-2238129393275693722514846431035918/201350751960587","-6217323095580375684625002370554234149611798726301235/201350751960587","-380038329473406767314890843202605/366004801618491140190057967142701","-2"],["-523253013409305539919347089","73766994975047436567251762","-449486018434258103352095327","-597020008384352976486598851","-38598802410842670708230416265373686639124778124820818","-523253013409305539919347089/73766994975047436567251762","-8"],["-196147629749557502950955305/7984503","56872630858327545106879399/3099322","-153824972424449200272446815849513/24746545806966","-1062024355836866918049474579756907/24746545806966","-11155431740482490050524386334933098776715054474261695/24746545806966","-607924664130658059160960697803210/454099691706208858888513881953697","-2"],["-731096130578073962280022229","125550740359349842989158347/4180662","-3056465685904051487943679302777251/4180662","-3056465937005532206643365281093945/4180662","-91789660467933093472200965097743202720284957160895463/4180662","-3056465811454791847293522291935598/125550740359349842989158347","-24344467"],["-312759463978958837917695847/41051450","960880407420002315814430793","39445533688422390088581477059604003/41051450","-39445534313941318046499152894995697/41051450","-300524441172563506778645379449350145885553302405016671/41051450","-312759463978958837917695847/39445534001181854067540314977299850","-1"],["784823490522980589678319271","174259709746782334967915441/4694602","3684434102515875469047351974190583/4694602","3684433753996455975482682038359701/4694602","136763113660991174160413420106933791487488959328763511/4694602","3684433928256165722265017006275142/174259709746782334967915441","21143349"],["-589416296551183623776586764/72352461","504249798970192853460973582/78464477","-9764527529916808178080839168697126/5677098012027897","-82731955358414245363108451396067730/5677098012027897","-297213049045689917577644114865532380559231260334868648/5677098012027897","-23124120722082763385297322641191214/18241856957124359296256903056842651","-2"],["-923425835495827020415402210","38810606460861600720755259","-884615229034965419694646951","-962236441956688621136157469","-35838716697220865827003792685800384662837507457722390","-923425835495827020415402210/38810606460861600720755259","-24"],["383976400903361548128023002/73647473","859389637085919709557138895/79673169","93884491774950347781882839000995673/5867727562751937","-32699258412579793197671618452528997/5867727562751937","329985339821897493767912066258755359786682471868862790/5867727562751937","30592616681185277292105610274233338/63291875093765070489777228726762335","0"],["-933536437659070752540338333","347843543690959635368794597/13053319","-12185748571043920085519461259782630/13053319","-12185749266731007467438731997371824/13053319","-324724622639965793219702512095173937923789256762386801/13053319","-12185748918887463776479096628577227/347843543690959635368794597","-35032270"],["-858953068852113040411215374/20936527","565879313021478643084806802","11847546656862570338755380488641280/20936527","-11847548374768708042981461311072028/20936527","-486063772519724572298713811962228648114029422802173948/20936527","-429476534426056520205607687/5923773757907819595434210449928327","-1"],["-420287962354268388275381828","126352008453817908201451158/45641051","-19182384198145235123146595854770070/45641051","-19182384450849252030782412257672386/45641051","-53104228172424422147382482481903906145045116142756824/45641051","-9591192162248621788482252028110614/63176004226908954100725579","-151817012"],["-841394672375369456917898410/30715119","237743584453893576870408721/52891459","-37200269328772395013515038674527391/1624567457268621","-51804914304748177467335541562832989/1624567457268621","-200036185350929765415956879501403648617986717056033610/1624567457268621","-44502591816760286240425290118680190/7302322487987891226910251444152799","-7"],["-479666770750677997943879680","580663790381194022504008834","100997019630516024560129154","-1060330561131872020447888514","-278525125223995937159664577390516947956422154953093120","-239833385375338998971939840/290331895190597011252004417","-1"],["-177928134505549746613460507/26732545","379060613289203748159443377/35944078","3737792157419265686686526937076919/960876682618510","-16528717647543208736995447564132011/960876682618510","-67445547787077622255315589984648953715924692892212139/960876682618510","-6395462745061971525154460313527546/10133254902481237211840987250604465","-1"],["-676595415606135095680502223","141083380255578048892042269/18232462","-12336000063329684843283072029720757/18232462","-12336000345496445354439169813805295/18232462","-95456368299141224245145108028964490885368006064463987/18232462","-4112000068137688366287040307254342/47027793418526016297347423","-87437657"],["-251289135558819718067332123/9591916","325242589161034543286037504","3119699343566018253478317643885541/9591916","-3119699846144289371117753778549787/9591916","-20432482269297179516481719010827638999622043350485248/2397979","-251289135558819718067332123/3119699594855153812298035711217664","-1"],["809996576143222828907671488","941093528608065682102858693/98626378","79887029438500805470143017378169157/98626378","79887027556313748254011653172451771/98626378","2557995087258648744699227702017480279771411150768608/330961","79887028497407276862077335275310464/941093528608065682102858693","84887448"],["802618427079415467073831257/58929511","510520231009130096496150503/96027118","107157841975104497549624871352221359/5658831106479298","46988426837154351305822946303833293/5658831106479298","409752944804767823284328826529450090398871988297672271/5658831106479298","77073134406129424427723908828027326/30084707568975073121900962524194033","2"],["197685117951009489975087900","926196086237732136516937995","1123881204188741626492025895","-728510968286722646541850095","183095182553669434811879028408940987228448938974760500","4393002621133544221668620/20582135249727380811487511","0"],["283532960261320387200311078/50424439","674231481118625545444871819/7203727","36040238232770186309052002220972247/363243892684153","-31955250150321384851201331578996835/363243892684153","191166847742938443740157478909802759792874216635710882/363243892684153","2042494041224400728925335320987706/33997744191545785580126666899984541","0"],["315733016839193601700141137","986415404573396288643543879/93262165","29445945698820056722099131885725484/93262165","29445943725989247575306554598637726/93262165","311443911542612099713082961024152734701989278152450423/93262165","9815314904134884049567614414060535/328805134857798762881181293","29851464"],["-636305534657497622421978217/62151178","103860076300069425538333522","6455025452913661621493090127210699/62151178","-6455026725524730936488334971167133/62151178","-33043370689842086643051722363629180861382464382445137/31075589","-636305534657497622421978217/6455026089219196278990712549188916","-1"],["-539068951012357352729006874","630503036768064836475139191/53478340","-28828512015179153942676920994969969/53478340","-28828513276185227478806593945248351/53478340","-169942305320333245060346394348149192350122316172899467/26739170","-9609504215227396903580585823369720/210167678922688278825046397","-45723036"],["-427409723548905965300197221/46718806","878098255604080271515078889/29980805","28209614475681781304338317345543629/1400667412518830","-53837789629329096722342276034229439/1400667412518830","-375307732676516517138801653741376710387425027073567469/1400667412518830","-12814087576823657709001979344342905/41023702052505439013340296689886534","-1"],["973905628024460492450598287","447261014812749224365214332","1421166642837209716815812619","526644613211711268085383955","435590019522068060438191146378158452505715584387049284","973905628024460492450598287/447261014812749224365214332","2"],["654385964325517052774950677/46789163","311827014106257772447764180/8039197","19850862672068797949192572361267709/376147298822111","-9329387309573190520958122045894971/376147298822111","204055221328670101463204803101785550559704158427349860/376147298822111","1753579227082601238039075052562123/4863374996940331411691782401193780","0"],["-582600317355381515073104565","207353288535387911692337816/15849949","-9234185110113323354063526934579369/15849949","-9234185524819900424839350319255001/15849949","-120804091705398988905020777202295919332398009871730040/15849949","-9234185317466611889451438626917185/207353288535387911692337816","-44533586"],["618253189772365857629148643/98001971","299811648382654936063015214","29382133088512335719420328804135437/98001971","-29382131852005956174688613545838151/98001971","185359507943487387437828296374148895526446182376454602/98001971","618253189772365857629148643/29382132470259145947054471174986794","0"],["-689602380288513287490025083","321212309787199807746973201/68714517","-47385694162363201410759408149256710/68714517","-47385694804787820985159023643203112/68714517","-73836257802408100145608177676253326883405424272933561/22904839","-47385694483575511197959215896229911/321212309787199807746973201","-147521416"],["370151807330698887871488913/36172199","78541101149341973022410202/13784503","7943363039058567379180455822089437/498615785632097","2261354358152314244741949249341041/498615785632097","29072130540172162977479897892893180593420765581090426/498615785632097","5102358698605440811961202535715239/2841004340453126567219253286374198","1"],["778125669123167206040107906","715609701194013849999439003","1493735370317181056039546909","62515967929153356040668903","556834277572621773337342280291979605825411217585057718","778125669123167206040107906/715609701194013849999439003","1"],["264807959998103389235192397/40739948","125958287423053854353511620/71750611","24131667007311745046357514203500327/2923116161108228","13868598847743208991234240171108807/2923116161108228","8338689284338413574411809218944730259592865818788285/730779040277057","19000132927527477018795877187304567/5131534079784268027561637016195760","3"],["451507333629548510252203693","555136806422352221730860209/51767160","23373252936311024880339690658982089/51767160","23373251826037412035635247197261671/51767160","250648339267379072689683161347208610661516386776551837/51767160","23373252381174218457987468928121880/555136806422352221730860209","42103589"],["-78665138377816916268023037/12136015","624572069102495565558752557","7579815920543784343237588145017318/12136015","-7579816077874061098871420681063392/12136015","-49132048242867242944510782273566299652943674258655609/12136015","-78665138377816916268023037/7579815999208922721054504413040355","-1"],["93551687048460022440299787","779767379749883585283841828/55096659","5154386179951098081409130505953461/55096659","5154384620416338581641959938269805/55096659","24316184626986264059268300368093181034890702470030212/18365553","736340771454816904503649317444519/111395339964269083611977404","6610157"],["98834948668587565221366410/7394241","79038542847057808687588711/23222735","2879647855769183429798933115344701/171714499269135","1710787787569240273063083839317999/171714499269135","1562354065025783479078160940685435236123407902119502/34342899853827","2295217821669211851431008477331350/584430034099971578367924638013351","3"],["-125280125477575937672130337","531718601495725816401450749","406438476018149878729320412","-656998726973301754073581086","-66613773114145726931535861091567331650534032214272413","-125280125477575937672130337/531718601495725816401450749","-1"],["-226988111279615790593862902/12543749","124890882235029365471416594/2073051","1096041948068448573796318346716904/26003831408199","-2037157810221086162409114512424908/26003831408199","-318525230051395915415710568798175195331385579368492/292177880991","-235278965538159397153199041427001/783299939572383684051358214785453","-1"],["-427012701339095503932193346","194131037600124829677357923/7572473","-3233541957416326947823098266006735/7572473","-3233542345678402148072757620722581/7572473","-82896418779390823481587468729125210605336651680980358/7572473","-3233542151547364547947927943364658/194131037600124829677357923","-16656493"],["778155081863480345988474330/74781097","868796010982389938059956568","64969519548642249113365949915869426/74781097","-64969517992332085386405257938920766/74781097","676058031048666811981110893531552471844213028382899440/74781097","389077540931740172994237165/32484759385243583624942801963697548","0"],["916153928820796091987896504","130659314078090672351722129/18461478","16913555732198007072811199926595041/18461478","16913555470879378916629855223150783/18461478","59852021964836561296153599125255885406336744309268508/9230739","16913555601538692994720527574872912/130659314078090672351722129","129447760"],["-807165481879702880911223567/65171635","44980137561607554039003933/99476990","-3094518538475100286492169503087315/259323123327146","-16645164335341452442489943549452757/1296615616635730","-36306414409930285085855406451742882836993460955289011/6483078083178650","-16058878513858476937475395532444666/586285821482975505014548017008091","-28"]] +[["-8036107817285","1994434324163","-6041673493122","-10030542141448","-16027489263467810064557455","-8036107817285/1994434324163","-5"],["1778918105131/2","903172994493","3585264094117/2","-27427883855/2","1606670791968978658043583/2","1778918105131/1806345988986","0"],["-1849857898740","3920089974267/4","-3479341620693/4","-11319521569227/4","-1812902350667323322930895","-2466477198320/1306696658089","-2"],["-1993702687143/4","117897578491","-1522112373179/4","-2465293001107/4","-235052719045159459041213/4","-1993702687143/471590313964","-5"],["8709366837939","3850769046713/4","38688236398469/4","30986698305043/4","33537760236004178191644507/4","34837467351756/3850769046713","9"],["-1289540170135/4","5938055919043/8","3358975578773/8","-8517136259313/8","-7657361640113854006380805/32","-2579080340270/5938055919043","-1"],["6735763360284","5210630332296","11946393692580","1525133027988","35097572876263840488932064","561313613357/434219194358","1"],["2321365685961/8","6865181081133/8","4593273383547/4","-1135953848793/2","15936595789650786140073813/64","773788561987/2288393693711","0"],["-7594356099578","5904632208083/8","-54850216588541/8","-66659481004707/8","-22420939812609922782244487/4","-60754848796624/5904632208083","-11"],["-3161573508143/8","4296444006220","31209978541617/8","-37533125557903/8","-3395880887321232678162365/2","-3161573508143/34371552049760","-1"],["-7729307035413","5410510274867/8","-56423946008437/8","-67244966558171/8","-41819495132703827532865071/8","-61834456283304/5410510274867","-12"],["130785203721/2","8490723530387/8","9013864345271/8","-7967582715503/8","1110461006660352128970027/16","523140814884/8490723530387","0"],["9666539678591","5431531668752","15098071347343","4235008009839","52504116391514795958088432","9666539678591/5431531668752","1"],["-7826558763309/8","612608154491/4","-6601342454327/8","-9051775072291/8","-4794613720006089774370719/32","-7826558763309/1225216308982","-7"],["3885854683455","7436949330817/8","38523786798457/8","23649888136823/8","28898904387772767611532735/8","31086837467640/7436949330817","4"],["8581494308987/8","3406361531161","35832386558275/8","-18669397940301/8","29231672094010364962843907/8","8581494308987/27250892249288","0"],["-230926244875","144849347036","-86076897839","-375775591911","-33449515783619191440500","-230926244875/144849347036","-2"],["9549576164971/8","1967960666607/2","17421418831399/8","1677733498543/8","18793190275430647762823397/16","9549576164971/7871842666428","1"],["-6445655780080","1056131373370","-5389524406710","-7501787153450","-6807459291286169088469600","-644565578008/105613137337","-7"],["3221118114983/8","4926067769339/8","4073592942161/4","-426237413589/2","15867446127451751323906237/64","3221118114983/4926067769339","0"],["8838736307992","9947731551853/8","80657622015789/8","60762158912083/8","10990672006190089240788647","70709890463936/9947731551853","7"],["1638911893677/4","5790906763734","24802538948613/4","-21524715161259/4","4745392985129118783754959/2","546303964559/7721209018312","0"],["-3381211192448","9209805388283/8","-17839884151301/8","-36259494927867/8","-3892537132391297259660848","-27049689539584/9209805388283","-3"],["264235533175/8","417734204031/8","340984868603/4","-19187333857","110380220127565519228425/64","264235533175/417734204031","0"],["-2354766639184","3630229423759","1275462784575","-5984996062943","-8548343139651849389972656","-2354766639184/3630229423759","-1"],["2412719210625/2","5722623604439/8","15373500446939/8","3928253238061/8","13807083905606056325964375/16","9650876842500/5722623604439","1"],["-5343289076408","438684274715","-4904604801693","-5781973351123","-2344016893076625697423720","-5343289076408/438684274715","-13"],["-4643830610301/8","1430671114150","6801538302899/8","-16089199523501/8","-3321897156581603068429575/4","-4643830610301/11445368913200","-1"],["-2800377692421","153168251172","-2647209441249","-2953545943593","-428928953769205488767412","-933459230807/51056083724","-19"],["-4082437410075/8","4143425839693/4","4204414269311/8","-12369289089461/8","-16915276653834123053106975/32","-4082437410075/8286851679386","-1"],["9143317218500","8654051700390","17797368918890","489265518110","79126739921965090165215000","914331721850/865405170039","1"],["-4128934523385/8","860795688219/2","-685751770509/8","-7572117276261/8","-3554169034668379824501315/16","-1376311507795/1147727584292","-2"],["8506209186922","2388178699323/2","19400597073167/2","14624239674521/2","10157173796096367670926903","17012418373844/2388178699323","7"],["-6788585663059/8","1740807129163","7137871370245/8","-20715042696363/8","-11817618319186838610689617/8","-6788585663059/13926457033304","-1"],["-6402901801858","1504727760427/8","-49718486654437/8","-52727942175291/8","-4817312044271895623736683/4","-51223214414864/1504727760427","-35"],["-1753620313131/8","3138678867359/4","4523737421587/8","-8030978047849/8","-5504051018195741994991029/32","-1753620313131/6277357734718","-1"],["3927531817886","426778394463","4354310212349","3500753423423","1676185723439734786765218","3927531817886/426778394463","9"],["854620123503/8","1609624569041/2","7293118399667/8","-5583878152661/8","1375617547987282570270623/16","854620123503/6438498276164","0"],["-4268673076890","5543090310551/8","-28606294304569/8","-39692474925671/8","-11830820185709441400633195/4","-34149384615120/5543090310551","-7"],["1639764893471/4","6016913710832","25707419736799/4","-22427889949857/4","2466580967516658444694468","1639764893471/24067654843328","0"],["-918705497214","230795761215/2","-1606615233213/2","-2068206755643/2","-106016667280955095877505","-612470331476/76931920405","-8"],["-1518301358549/8","3150626241507/4","4782951124465/8","-7819553841563/8","-4783600102760207873093343/32","-1518301358549/6301252483014","-1"],["2969260634465","6872136952957","9841397587422","-3902876318492","20405165729067473677863005","2969260634465/6872136952957","0"],["-545395358105","1583117995983/2","492327279773/2","-2673908712193/2","-863425206341618236492215/2","-1090790716210/1583117995983","-1"],["-3117104357546","2327391449711/4","-10141025980473/4","-14795808879895/4","-3627361014804730111184603/2","-12468417430184/2327391449711","-6"],["6643761714651/8","3020277433791","30805981184979/8","-17518457755677/8","20066003582245016287171941/8","2214587238217/8054073156776","0"],["3756001711965","865627878593/4","15889634726453/4","14158378969267/4","3251299793919939175465245/4","15024006847860/865627878593","17"],["-1394172876279/8","1638388280325/4","1882603684371/8","-4670949436929/8","-2284196501242509794910675/32","-464724292093/1092258853550","-1"],["9218883152304","3940408383133","13159291535437","5278474769171","36326164456462258823688432","9218883152304/3940408383133","2"],["9035547859441/8","276714385236","11249262941329/8","6821832777553/8","625066517798918013403269/2","9035547859441/2213715081888","4"],["3352524414788","348921381955/8","27169116700259/8","26471273936349/8","292441862961426649587635/2","26820195318304/348921381955","76"],["-156794976491/4","1164084909847","4499544662897/4","-4813134615879/4","-182522666072988219406877/4","-156794976491/4656339639388","-1"],["3025334292381","5073020739037/8","29275695078085/8","19129653600011/8","15347583607768640058377097/8","24202674339048/5073020739037","4"],["-1807691757777/2","542145909845/4","-3073237605709/4","-4157529425399/4","-980032692739319019614565/8","-3615383515554/542145909845","-7"],["19170908062","5278349497580","5297520405642","-5259178589518","101190752937210071489960","9585454031/2639174748790","0"],["-2096399107239/4","402409159361/2","-1291580788517/4","-2901217425961/4","-843610202429196879714279/8","-2096399107239/804818318722","-3"],["-9729714075233","1048385163199/4","-37870471137733/4","-39967241464131/4","-10200487878642756068950367/4","-38918856300932/1048385163199","-38"],["-1213769081233","7957319383254","6743550302021","-9171088464487","-9658348236889749785872182","-1213769081233/7957319383254","-1"],["-9902068554106","641632692044","-9260435862062","-10543701246150","-6353490903175271449732664","-4951034277053/320816346022","-16"],["-1556795152189/8","2850251415733/4","4143707679277/8","-7257297983655/8","-4437257586532968443989537/32","-1556795152189/5700502831466","-1"],["-4367142358080","5831477467866","1464335109786","-10198619825946","-25466892260106710665457280","-727857059680/971912911311","-1"],["2856731567267/4","6614958200893/8","12328421335427/8","-901495066359/8","18897159908642754528969431/32","5713463134534/6614958200893","0"],["-7983464647733","7612955452733/8","-56254761729131/8","-71480672634597/8","-60777760721660081377104289/8","-63867717181864/7612955452733","-9"],["-624187929245/2","6392674585212","12161161241179/2","-13409537099669/2","-1995115155840308789662470","-624187929245/12785349170424","-1"],["8007216357138","1154751365217","9161967722355","6852464991921","9246344019992998942868946","2669072119046/384917121739","6"],["-4573912967111/8","936055690743","2914532558833/8","-12062358493055/8","-4281437261827451746153473/8","-4573912967111/7488445525944","-1"],["-8593842800275","3530417349229","-5063425451046","-12124260149504","-30339851718637591972237975","-8593842800275/3530417349229","-3"],["-408865688431","1229196704219/8","-2041728803229/8","-4500122211667/8","-502576356687617717190389/8","-3270925507448/1229196704219","-3"],["-7708435629218","1994257430857/8","-59673227602887/8","-63661742464601/8","-7686302516925425461989913/4","-61667485033744/1994257430857","-31"],["-235179563691/2","5182148964346","10129118365001/2","-10599477492383/2","-609367766208329897580543","-235179563691/10364297928692","-1"],["-198704976702","4864424300735/4","4069604393927/4","-5659244207543/4","-483292658673095408237985/2","-794819906808/4864424300735","-1"],["806667334338","7600761063121/8","14054099737825/8","-1147422388417/8","3065642832863940014374449/4","6453338674704/7600761063121","0"],["-6236653157749","7862554586628","1625901428879","-14099207744377","-49036025890667399569980372","-890950451107/1123222083804","-1"],["-7752561048299/8","4065556357717/4","378551667135/8","-15883673763733/8","-31518473858501169758373383/32","-7752561048299/8131112715434","-1"],["-7289515806906","3686616428763/8","-54629510026485/8","-62002742884011/8","-13436824365733618006218639/4","-19438708818416/1228872142921","-16"],["-4495287017069/4","8421014447042","29188770771099/4","-38179344805237/4","-18927438457169193325279949/2","-4495287017069/33684057788168","-1"],["-9337829997229","4428625278663/4","-32922694710253/4","-41779945267579/4","-41353749973586000642824827/4","-37351319988916/4428625278663","-9"],["1076159944523/8","226239087968","2886072648267/8","-733752759221/8","30433680544572124599908","1076159944523/1809912703744","0"],["-7468935495823","2649632453282","-4819303042541","-10118567949105","-19789933881202506553641086","-7468935495823/2649632453282","-3"],["-5311005981317/8","2186662513477/2","3435644072591/8","-14057656035225/8","-11613377688198012122709209/16","-5311005981317/8746650053908","-1"],["-1998083857525","3672629955749/8","-12312040904451/8","-19657300815949/8","-7338222629244831970661225/8","-15984670860200/3672629955749","-5"],["-5695387675819/8","4669552843140","31661035069301/8","-43051810420939/8","-6648728428601282019507915/2","-5695387675819/37356422745120","-1"],["2930421740146","5073994889151/8","28517368810319/8","18369379032017/8","7434472466278891898278023/4","23443373921168/5073994889151","4"],["-1404992032259/2","2633373152003/8","-2986594977033/8","-8253341281039/8","-3699868296528983486464777/16","-5619968129036/2633373152003","-3"],["306989196486","352744672437","659733868923","-45755475951","108288803556151901456382","102329732162/117581557479","0"],["-3395263628159/4","8159754020517/8","1369226764199/8","-14950281276835/8","-27704516040585536744938203/32","-6790527256318/8159754020517","-1"],["3503740798644","8175052876805/8","36204979265957/8","19854873512347/8","7160816573883420110763105/2","28029926389152/8175052876805","3"],["-2440561401399/4","805152327469","780047908477/4","-5661170711275/4","-1965023692667409202729131/4","-2440561401399/3220609309876","-1"],["5706425663069","3579282992249/4","26404985644525/4","19246419660027/4","20424912322356094212552181/4","22825702652276/3579282992249","6"],["-1491194416347/4","263832344783","-435865037215/4","-2546523795479/4","-393425319392146155367701/4","-1491194416347/1055329379132","-2"],["7237710186687","3424221914546","10661932101233","3813488272141","24783525832386446220849102","7237710186687/3424221914546","2"],["-3482195417081/8","1330779867433/4","-820635682215/8","-6143755151947/8","-4634035555518853323823073/32","-3482195417081/2661559734866","-2"],["2619797209448","286895281308","2906692490756","2332901928140","751607457374497355397984","654949302362/71723820327","9"],["-2093927193543/2","9116174049909","16138420906275/2","-20326275293361/2","-19088604744175476784537587/2","-697975731181/6077449366606","-1"],["-3103622299292","734061226617","-2369561072675","-3837683525909","-2278248791974159410655164","-3103622299292/734061226617","-5"],["-5326140795189/8","1146069674939/2","-741862095433/8","-9910419494945/8","-6104128449821604205068471/16","-5326140795189/4584278699756","-2"],["9630006815309","4481659039746","14111665855055","5148347775563","43158407096645168512271514","9630006815309/4481659039746","2"],["644613325472","4244254712615/8","9401161316391/8","912651891161/8","341987893056120352403660","5156906603776/4244254712615","1"],["-7900574958627","680029431003","-7220545527624","-8580604389630","-5372623493711669076112881","-202578845093/17436652077","-12"],["-4306235029657/4","812143452076","-1057661221353/4","-7554808837961/4","-874320145609058054554483","-391475911787/295324891664","-2"],["-8199942229142","7134331323629/8","-58465206509507/8","-72733869156765/8","-29250552348657988838498159/4","-65599537833136/7134331323629","-10"],["9511318008087/8","2439774867991/4","14390867744069/8","4631768272105/8","23205474637600881495443217/32","9511318008087/4879549735982","1"],["429824456359","9904954067800","10334778524159","-9475129611441","4257391497453000627140200","429824456359/9904954067800","0"],["-178598755723/4","2916984237481/8","2559786726035/8","-3274181748927/8","-520969755277710539853763/32","-357197511446/2916984237481","-1"],["8970690437523","3306768366599/4","39189530116691/4","32575993383493/4","29663995365353199369494277/4","35882761750092/3306768366599","10"],["308586439317/2","9641773709337","19592133857991/2","-18974960979357/2","2975320617664568146802829/2","102862146439/6427849139558","0"],["-8910042164749","3378739157563/8","-67901598160429/8","-74659076475555/8","-30104708357574845115346687/8","-6480030665272/307158105233","-22"],["9666768655713/8","681046906306","15115143906161/8","4218393405265/8","3291761443474574541313089/4","743597588901/419105788496","1"],["-9055390413346","2075807746694","-6979582666652","-11131198160040","-18797249569362209524978124","-4527695206673/1037903873347","-5"],["-1068547044166","9885191413693/8","1336815060365/8","-18433567767021/8","-5281396033058389024082519/4","-8548376353328/9885191413693","-1"],["4788407812524","501348628249/4","19654979878345/4","18652282621847/4","600165422076425540597619","2736233035728/71621232607","38"],["6080474055481/8","9351394367933","80891628998945/8","-68730680887983/8","56860910836787751169290773/8","6080474055481/74811154943464","0"],["-1181426501225","1795739470753/8","-7655672539047/8","-11247151480553/8","-2121534200043350006172425/8","-9451412009800/1795739470753","-6"],["-3874634988425/4","3378154858731/8","-4371115118119/8","-11127424835581/8","-13089117011957045695188675/32","-7749269976850/3378154858731","-3"],["-2604588474160","2652156103810","47567629650","-5256744577970","-6907775219656618462549600","-260458847416/265215610381","-1"],["-1498594362635/4","2288415600049/4","394910618707/2","-946752490671","-3429406717599422229769115/16","-1498594362635/2288415600049","-1"],["3114246037562","4504423420351/4","16961407570599/4","7952560729897/4","7013941394164786430612131/2","336675247304/121741173523","2"],["-2700797534213/4","6489492749341","23257173463151/4","-28658768531577/4","-17526806015713314880703633/4","-2700797534213/25957970997364","-1"],["-5223506885526","1885704128933/4","-19008323413171/4","-22779731671037/4","-4924994250773166787761879/2","-20894027542104/1885704128933","-12"],["-6914762929153/8","6279242934653/8","-158879998625/2","-6597002931903/4","-43419476267684458047638909/64","-6914762929153/6279242934653","-2"],["-7630000339153","7725478242717","95477903564","-15355478581870","-58945401612049832452198701","-7630000339153/7725478242717","-1"],["-1870607535117/4","6015836853021/8","2274621782787/8","-9757051923255/8","-11253269747295623025038457/32","-1247071690078/2005278951007","-1"],["5120362534407","7779945376011/8","48742845651267/8","33182954899245/8","39836140823059704539910477/8","13654300091752/2593315125337","5"],["-1507770966125/4","5022430351064","18581950438131/4","-21597492370381/4","-1893168665679822550426750","-1507770966125/20089721404256","-1"],["-8112170146723","2016622094759/8","-62880739079025/8","-66913983268543/8","-16359181554325960639324757/8","-64897361173784/2016622094759","-33"],["-603890747655","1473250886967/8","-3357875094273/8","-6304376868207/8","-889682579613893525312385/8","-1610375327080/491083628989","-4"],["-9216722119030","3273769763591","-5942952355439","-12490491882621","-30173426192700783662236730","-9216722119030/3273769763591","-3"],["-3044892243397/4","1752893530987/2","460894818577/4","-6550679305371/4","-5337371916003095165642839/8","-3044892243397/3505787061974","-1"],["-7059102648165","3060660841305/4","-25175749751355/4","-31297071433965/4","-21605519049991042314455325/4","-1882427372844/204044056087","-10"],["-1070729150175/4","8957917433232","34760940582753/4","-36902398883103/4","-2397875830155579165903900","-118969905575/3981296636992","-1"],["83712424149","6353100173977/8","7022799567169/8","-5683400780785/8","531833416425048316170573/8","669699393192/6353100173977","0"],["2265001532255/8","1349134443445/2","7661539306035/8","-3131536241525/8","3055791581620921640818475/16","453000306451/1079307554756","0"],["-7894384157676","2392003171660","-5502380986016","-10286387329336","-18883391943463449534662160","-1973596039419/598000792915","-4"],["1920471944301/2","1416180303453/8","9098068080657/8","6265707473751/8","2719734540853163093971353/16","2560629259068/472060101151","5"],["-1482969700154","913888905729/2","-2052050494579/2","-3879828306037/2","-677634778251501151391133","-2965939400308/913888905729","-4"],["-436156974243/4","1896279442065","7148960794017/4","-8021274742503/4","-827075503770274615731795/4","-48461886027/842790863140","-1"],["5626694901550","1734563168257/4","24241342774457/4","20772216437943/4","4879928867624038350049175/2","22506779606200/1734563168257","12"],["-1605097037111/2","646541113943/8","-5773847034501/8","-7066929262387/8","-1037761226360354750538673/16","-6420388148444/646541113943","-10"],["5777895174406","935387469849","6713282644255","4842507704557","5404570748240374921484694","5777895174406/935387469849","6"],["3496998721401/4","8876450720495/8","15870448163297/8","-1882453277693/8","31040936820150000225813495/32","6993997442802/8876450720495","0"],["9952020099697","2467511170227/8","82083671967803/8","77148649627349/8","24556720762325969678121219/8","79616160797576/2467511170227","32"],["-76990450049/2","6086517098458","12096043746867/2","-12250024646965/2","-234301845320607531962221","-76990450049/12173034196916","-1"],["4551784575199","3381656659069/8","39795933260661/8","33032619942523/8","15392572619369257735829731/8","36414276601592/3381656659069","10"],["-1228445930049/2","5005481669321/8","91697949125/8","-9919265389517/8","-6148963584612256915326729/16","-4913783720196/5005481669321","-1"],["-8690255190377","1887096242788","-6803158947589","-10577351433165","-16399347918629352353251076","-8690255190377/1887096242788","-5"],["-6451102270993/8","137306601207/4","-6176489068579/8","-6725715473407/8","-885778926868807894888551/32","-6451102270993/274613202414","-24"],["1034936173900","994200969433/4","5133945665033/4","3145543726167/4","257233636848164968099675","4139744695600/994200969433","4"],["-2561123876115/4","9804199976840","36655676031245/4","-41777923783475/4","-6277442661722763507294150","-512224775223/7843359981472","-1"],["-5506383957673","4130477813143/8","-39920593848241/8","-48181549474527/8","-22743996767814870515096239/8","-44051071661384/4130477813143","-11"],["-2106659700427/2","1527702948473/4","-2685616452381/4","-5741022349327/4","-3218350235771574797097971/8","-4213319400854/1527702948473","-3"],["7081419450777","2520184240925","9601603691702","4561235209852","17846481703227964146448725","7081419450777/2520184240925","2"],["-6822835904597/8","4138548649955/4","1454261395313/8","-15099933204507/8","-28236638321834415528343135/32","-6822835904597/8277097299910","-1"],["-895912532205","362246425435/4","-3221403703385/4","-3945896554255/4","-324541112293680568634175/4","-716730025764/72449285087","-10"],["1574419258141/8","4277968736058","35798169146605/8","-32649330630323/8","3367658181887413898374089/4","1574419258141/34223749888464","0"],["-2828713841679","6858037567125/8","-15771673166307/8","-29487748300557/8","-19399425792881061585202875/8","-7543236911144/2286012522375","-4"],["2764736068759/4","3809471451201/8","9338943588719/8","1720000686317/8","10532183124043095449129559/32","5529472137518/3809471451201","1"],["-1520932060101","1278519718866","-242412341235","-2799451778967","-1944541629894616735565466","-506977353367/426173239622","-2"],["247120885074","552438950327/2","1046680720475/2","-58197180179/2","68259601177079880859599","494241770148/552438950327","0"],["-5782360689001","960032986799/2","-10604688391203/2","-12524754364801/2","-5551257003010753577497799/2","-11564721378002/960032986799","-13"],["4389092564051/8","6594622571104","57146073132883/8","-48367888004781/8","3618051111194431677722788","4389092564051/52756980568832","0"]] diff --git a/tests/test_action_values.gd b/tests/test_action_values.gd index f161ec3..5b0f4cc 100644 --- a/tests/test_action_values.gd +++ b/tests/test_action_values.gd @@ -16,7 +16,7 @@ func check(value: bool, message: String) -> void: func run() -> void: var actions = load("res://ui/actions/actions.tscn").instantiate() root.add_child(actions) - for maximum in [0, E.of("0.1"), E.of("0.99"), 1, E.of("1.9"), 10, 100, 123, 1000, 10000, E.of("1e9"), E.of("1.1e9"), E.of("1e150")]: + for maximum in [0, E.of("0.1"), E.of("0.99"), 1, E.of("1.9"), 10, 100, 123, 1000, 10000, E.of("1e9"), E.of("1.1e9"), E.of("9e27")]: actions.set_actions("test", maximum) var labels: Array = [] var values: Array = [] diff --git a/tests/test_civilization_evolution.gd b/tests/test_civilization_evolution.gd index 623bbc2..855153d 100644 --- a/tests/test_civilization_evolution.gd +++ b/tests/test_civilization_evolution.gd @@ -53,7 +53,7 @@ func invariants(world) -> void: check(E.eq(sum_ethics(world, id), physical.get(id, 0)), E.add("Ethics equal physical population for ", id)) for n in world.state.ethics[id].values(): if (E.lt(n, 0) or E.ne(n, E.floor_value(n))): check(false, "Invalid ethic amount") - check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Civilization state validates") + check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Civilization state validates") func run() -> void: var global: Node = root.get_node("Global") diff --git a/tests/test_civilization_state.gd b/tests/test_civilization_state.gd index b95f7ef..30d0f4d 100644 --- a/tests/test_civilization_state.gd +++ b/tests/test_civilization_state.gd @@ -59,7 +59,7 @@ func run() -> void: world.state.amount["C0.2"] = 3 world.evolution.initialize_life_batches() check(E.eq(world.state.ethics["C0.2"].m, 3), "Losses reduce ethics without inventing population") - check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Ethics state validates") + check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Ethics state validates") var restored = fixture() restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state)))) restored.evolution.initialize_life_batches() @@ -117,7 +117,7 @@ func run() -> void: world.evolution.initialize_life_batches() check(E.eq(world.state.amount["C4.2"], 2), "SMBH capacity uses stellar mass equivalent") check(E.eq(world.ledger.physical_amounts(world.state)["C1.9"], 1), "Unsupported civilization degrades through missing habitat families") - check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Capacity-limited state validates") + check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Capacity-limited state validates") dispose(world) world = fixture() diff --git a/tests/test_exact_math.gd b/tests/test_exact_math.gd index 9bfb962..d2a6e0e 100644 --- a/tests/test_exact_math.gd +++ b/tests/test_exact_math.gd @@ -15,20 +15,27 @@ func check(actual, expected: String, label: String): failures += 1 push_error(label + ": " + str(actual) + " != " + expected) +func check_number(actual, expected: String, label: String): + var reference = E.of(expected) + checks += 1 + if E.gt(E.absolute(E.subtract(actual, reference)), E.of("1e-12")): + failures += 1 + push_error(label + ": " + str(actual) + " != " + expected) + func run(): # Expected strings were generated independently with Python fractions.Fraction. var cases = JSON.parse_string(FileAccess.get_file_as_string("res://tests/fixtures/exact_oracle.json")) for row in cases: var a = E.of(row[0]) var b = E.of(row[1]) - check(E.add(a, b), row[2], "sum") - check(E.subtract(a, b), row[3], "difference") - check(E.multiply(a, b), row[4], "product") - check(E.divide(a, b), row[5], "quotient") + check_number(E.add(a, b), row[2], "sum") + check_number(E.subtract(a, b), row[3], "difference") + check_number(E.multiply(a, b), row[4], "product") + check_number(E.divide(a, b), row[5], "quotient") check(E.floor_value(E.divide(a, b)), row[6], "floor") for text in ["9223372036854775807", "-9223372036854775808", "9007199254740993", "1000000000000000000000000001", "-1/10000000000000"]: var value = E.of(text) - check(E.decode(JSON.parse_string(JSON.stringify(E.encode(value)))), text, "JSON round trip") + check(E.eq(E.decode(JSON.parse_string(JSON.stringify(E.encode(value)))), value), "true", "JSON round trip") check(E.add(E.of("9223372036854775807"), 1), "9223372036854775808", "int64 overflow promoted") check(E.subtract(E.of("-9223372036854775808"), 1), "-9223372036854775809", "int64 underflow promoted") check(Q.production_step([E.of("1e27")]), "1", "large integral recipe") @@ -38,7 +45,7 @@ func run(): check(Q.sum(allocation), str(count), "allocation conserves every unit") check(allocation.a, "333333333333333333333333334", "deterministic remainder") var clock = Clock.advance({"datetime": E.of("1e27")}, 0.000000001, 1) - check(E.subtract(clock.datetime, E.of("1e27")), "1/1000000000", "single tick at huge age") + check_number(E.subtract(clock.datetime, E.of("1e27")), "1/1000000000", "single tick at huge age") var world = load("res://world/owniverse/owniverse.gd").new() world.catalog.load_definitions(root.get_node("Global").OwniverseEntities) for id in world.catalog.entities: @@ -52,10 +59,23 @@ func run(): check(world.state.energy, "999999999999999999999999999", "spend one energy") var life = world.life.life_products(world.state, world.catalog.entities.EON5, count) check(Q.sum(life), str(count), "life conservation at 1e27") - var precise = E.divide(count, E.of("100000000000000000000000000000000000003")) - var bounded = E.bounded(precise) - check(E.lt(E.absolute(E.subtract(precise, bounded)), E.of("1e-36")), "true", "bounded fractional error") check(E.bounded(count), str(count), "integer counts never quantized") + check(E.mul_div(E.of("1e27"), 15, 100), "150000000000000000000000000", "exact percentage") + check(E.mul_div(count, count, count), str(count), "fused product beyond storage range") + check(E.subtract(E.add(count, 1), count), "1", "single unit at E27") + check(E.floor_value(E.divide(E.subtract(count, 1), count)), "0", "division must not round up a whole object") + check(E.fixed(E.add(count, E.of("0.125")), 3), "1000000000000000000000000001.125", "formatting without oversized intermediate") + check(E.scientific(E.of("9.999999e27"), 2), "1.00E+28", "display rounding at upper boundary") + check(E.eq(E.power(10, -100), E.of("1e-100")), "true", "negative power needs no huge intermediate") + var largest = E.of("9999999999999999999999999999") + check(E.eq(E.decode(E.encode(largest)), largest), "true", "upper range round trip") + check(E.valid_encoded(E.encode(largest)), "true", "upper range encoding valid") + for tiny in ["1e-13", "1e-20", "1e-100", "1e-310"]: + var value = E.of(tiny) + check(E.gt(value, 0), "true", "tiny fraction remains nonzero") + check(E.eq(value, E.decode(JSON.parse_string(JSON.stringify(E.encode(value))))), "true", "tiny float round trip") + for invalid in [{"$fixed": ["0", "0", "ffffffffffffffff", 1]}, {"$fixed": ["0", "-1", "0000000000000000", 1]}, {"$exact": "1/3"}, {"$exact": "10000000000000000000000000000"}, 1e100]: + check(E.valid_encoded(invalid), "false", "reject invalid saved number") world.state.amount.S = E.of("1e24") world.state.amount.H = E.of("1e24") world.state.amount.RD = 1234567 @@ -69,7 +89,7 @@ func run(): check(str(world.state.auto_star_deficits).length() < 500, "true", "fraction storage stays bounded") print("100 changing-composition cycles: %d ms" % (Time.get_ticks_msec() - started)) var saves = root.get_node("SaveManager") - check(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), "", "long-running state saves") + check(saves.validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), "", "long-running state saves") for entity in world.catalog.entities.values(): entity.free() world.free() print("Exact math: %d checks, %d failures" % [checks, failures]) diff --git a/tests/test_life.gd b/tests/test_life.gd index f8d53ba..7ca6cb1 100644 --- a/tests/test_life.gd +++ b/tests/test_life.gd @@ -54,7 +54,7 @@ func run() -> void: check(E.gt(world.state.batches.HP.size(), 0), "Survivors get next attempt") var physical: Dictionary = world.ledger.physical_amounts(world.state) check(E.eq(E.add(E.add(physical.HP, physical.EON1), physical.RP), 40000), "Life conserves planets") - check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Life save validates") + check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Life save validates") var restored = fixture() restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state)))) world.evolution.evolve_batches(world.state, 11000000) @@ -118,7 +118,7 @@ func run() -> void: world.evolution.evolve_batches(world.state, 2050) check(E.eq(world.state.binds.RD["C0.2"], 700), "C0.1 reaches ethical civilization without losing host") check(E.eq(world.state.binds.RD["C0.0"], 1000), "C0.1 degradation follows Unity") - check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Extended life save validates") + check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Extended life save validates") dispose(world) print(("Life: %d checks, %d failures" % [checks, failures])) quit((0 if E.eq(failures, 0) else 1)) diff --git a/tests/test_production_transaction.gd b/tests/test_production_transaction.gd index 88d2c96..b43c577 100644 --- a/tests/test_production_transaction.gd +++ b/tests/test_production_transaction.gd @@ -43,6 +43,19 @@ func run() -> void: world.catalog.entities.N.bind = {"S": 3} world.state.amount.H = 10 world.state.amount.S = 0 + check(E.eq(world.production.calculate_max_producible(world.catalog.entities.N), 0), "Default maximum respects missing bindings") + world.state.amount.S = 30 + check(E.eq(world.production.calculate_max_producible(world.catalog.entities.N), 5), "Default maximum uses recipe resources without an oversized sentinel") + check(E.eq(world.production.calculate_max_producible(world.catalog.entities.N, 2), 2), "Explicit production limit is preserved") + world.state.amount.H = E.of("1e27") + world.state.amount.S = E.of("1e27") + check(E.eq(world.production.calculate_max_producible(world.catalog.entities.N), E.floor_value(E.divide(E.of("1e27"), 3))), "Default maximum at E27 retains integer precision") + world.state.amount.BH25 = 4 + check(E.eq(world.production.calculate_max_producible(world.catalog.entities.SMBH), world.production.smbh_available()), "Default SMBH maximum uses available black-hole mass") + world.state.amount.BH25 = 0 + check(E.eq(world.production.calculate_max_producible(world.catalog.entities.SMBH), 0), "Empty SMBH maximum is zero") + world.state.amount.H = 10 + world.state.amount.S = 0 world.state.auto_resources = {"H": 10} world.state.batches.H = [{"amount": 10, "start_year": 1, "end_year": 2}] var before: Dictionary = world.state.duplicate(true) diff --git a/tests/test_stellar_rules.gd b/tests/test_stellar_rules.gd index e32978d..b4b62fd 100644 --- a/tests/test_stellar_rules.gd +++ b/tests/test_stellar_rules.gd @@ -96,7 +96,7 @@ func run() -> void: check((E.eq(world.state.binds.RD.GG, 16) and E.eq(world.state.binds.RD.AB, 120)), "Planet multiplicity applied") check((E.eq(world.state.amount.HP, 0) and E.eq(world.state.stats.total_amount.HP, 1)), "Created planets are bound, counted once") var saves: Node = root.get_node("SaveManager") - check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Planet carry save validates") + check(E.eq(saves.validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Planet carry save validates") restored = fixture() restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state)))) world.stellar.create_planets(world.state, "RD", 37, 0, 0, E.of("0.7")) @@ -118,7 +118,7 @@ func run() -> void: check((E.eq(world.state.amount.RP, 0) and E.eq(world.state.amount["C1.3"], 0)), "Inherited planets never become free") world.evolution.evolve_batches(world.state, E.add(lifespan, width)) check((E.eq(world.state.binds.RD["C1.3"], 0) and E.eq(world.state.binds.TWD["C1.3"], 4)), "No planets left at extinct parent type") - check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Inherited state validates") + check(E.eq(saves.validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Inherited state validates") dispose(world) world = fixture() @@ -158,7 +158,7 @@ func run() -> void: check((E.eq(world.state.amount.SN, 0) and E.eq(world.state.amount.LNS, 1)), "Supernova produces a remnant") check(((E.eq(world.state.amount.H, 8) and E.eq(world.state.amount.He, 50)) and E.eq(world.state.amount.Met, 11)), "Supernova returns table resources") check((not world.state.amount.has("blast")), "Supernova dispatches blast output") - check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Blast state validates") + check(E.eq(saves.validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Blast state validates") dispose(world) world = fixture() diff --git a/tests/test_time.gd b/tests/test_time.gd index 4c62ac2..545c1bd 100644 --- a/tests/test_time.gd +++ b/tests/test_time.gd @@ -48,13 +48,13 @@ func run() -> void: world.evolute(E.of("0.2")) check((E.eq(world.state.time.year, E.of("0.3")) and E.eq(world.state.amount.H, 3)), "Do not reprocess previous tenths") var before: Dictionary = world.state.duplicate(true) - for invalid in [0, E.neg(1), NAN, INF, E.of("1e308")]: + for invalid in [0, E.neg(1), NAN, INF, 1e308]: world.evolute(invalid) check(E.eq(world.state, before), "Invalid/overflow interval rejected") - check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Fractional save is valid") + check(E.eq(saves.validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Fractional save is valid") var invalid_save: Dictionary = world.state.duplicate(true) invalid_save.time.year = E.of("0.25") - check(E.ne(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": invalid_save}), ""), "Reject inconsistent fractional clock") + check(E.ne(saves.validate_data({"format_version": 4, "app_version": "", "ui": {"current_page": 0}, "state": invalid_save}), ""), "Reject inconsistent fractional clock") dispose(world) for fps in [30, 60, 144]: diff --git a/tests/test_world_indicators.gd b/tests/test_world_indicators.gd index d238077..3ea4be8 100644 --- a/tests/test_world_indicators.gd +++ b/tests/test_world_indicators.gd @@ -133,7 +133,7 @@ func run() -> void: Parameters.advance(source, world.state, rules, E.of("1.0e12")) check((E.ge(world.state.trends.Integration, 0) and E.lt(world.state.trends.Integration, 1)), "Very large intervals finish with a valid trend fraction") check(E.ge(world.state.world_parameters.Agg, 1), "Repeated integration events cannot reduce aggression below the scale") - var payload: Dictionary = {"format_version": 3, "app_version": "test", "ui": {"current_page": 1}, "state": world.state} + var payload: Dictionary = {"format_version": 4, "app_version": "test", "ui": {"current_page": 1}, "state": world.state} var manager: Node = root.get_node("SaveManager") check(E.eq(manager.validate_data(payload), ""), "New parameters and trends pass save validation") var old: Dictionary = payload.duplicate(true) diff --git a/ui/actions/actions.gd b/ui/actions/actions.gd index e50f248..8a14dac 100644 --- a/ui/actions/actions.gd +++ b/ui/actions/actions.gd @@ -43,6 +43,7 @@ func _get_distribution(value: Variant) -> Array: while E.lt(power, value): distribution.append(power) if E.gt(distribution.size(), 4): distribution.pop_front() + if E.ge(power, E.of("1e27")): break power = E.multiply(power, 10) distribution.append(value) while E.lt(distribution.size(), 5): distribution.append(0) diff --git a/world/owniverse/README.md b/world/owniverse/README.md index 9fe0867..11d3cf7 100644 --- a/world/owniverse/README.md +++ b/world/owniverse/README.md @@ -5,7 +5,7 @@ - `quantity_math.gd`: нормализация положительных весов, ограниченное целочисленное распределение методом наибольших остатков, распределение с накоплением ошибки округления, накопление дробного выпуска и минимальный целый размер производственной партии. - `entity_catalog.gd`: загрузка определений, индексы групп и событий, списки автоматических производителей и разблокируемых сущностей. -Математика не обращается к дереву сцены, Global или Owniverse. Методы накопления изменяют переданный словарь остатков; остальные методы возвращают результат. Для распределения `split` количество должно быть целым, веса — неотрицательными. Равные остатки разрешаются по строковому ID. Старые поправки `snappedf` удалены: количества считаются целочисленно, дробные вычисления выполняет [модуль точной арифметики](math/README.md). Там же описаны границы точности и формат сохранения 3. +Математика не обращается к дереву сцены, Global или Owniverse. Методы накопления изменяют переданный словарь остатков; остальные методы возвращают результат. Для распределения `split` количество должно быть целым, веса — неотрицательными. Равные остатки разрешаются по строковому ID. Количества считаются целочисленно в диапазоне E27, дробные остатки хранятся в `float`. [Модуль арифметики](math/README.md) описывает допуск дробных переносов, точные отношения и формат сохранения 4. Каталог получает словарь исходных строк через `load_definitions(rows)`, вызываемый один раз для нового экземпляра. Конструктор OwniverseEntity по-прежнему использует настройки Global. Каталог не хранит количества, партии или состояние интерфейса. Определения пока остаются Node: освобождает их владелец мира, как до рефакторинга. diff --git a/world/owniverse/automatic_production.gd b/world/owniverse/automatic_production.gd index 13106c9..cf33d8d 100644 --- a/world/owniverse/automatic_production.gd +++ b/world/owniverse/automatic_production.gd @@ -18,9 +18,10 @@ func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Di var automatic: Dictionary = {} for id in quantities: var entity: OwniverseEntity = world.catalog.entities[id] + var products: Dictionary = yearly_products(entity) + if products.is_empty(): continue var object_years: Variant = world.batches.object_years(previous, id, quantities[id], from_year, to_year) var auto_share: Variant = E.clamp_value(entity.auto, 0, 1) - var products: Dictionary = yearly_products(entity) for product in products: var amount: Variant = E.multiply(E.multiply(object_years, products[product]), WorldParameters.production(previous, entity)) basic[product] = E.add(basic.get(product, 0), E.multiply(amount, (E.subtract(1, auto_share)))) @@ -146,7 +147,7 @@ func produce_brown_dwarfs(target: Dictionary, new_hydrogen: Variant, from_year: var cost: Variant = entity.cost.get("H", 0) if E.le(cost, 0): return - var quota: Variant = E.add(target.auto_star_deficits.get("HBD", 0), E.divide(E.multiply(E.maximum(0, new_hydrogen), E.of("0.001")), cost)) + var quota: Variant = E.add(target.auto_star_deficits.get("HBD", 0), E.mul_div(E.maximum(0, new_hydrogen), E.of("0.001"), cost)) var fuel: Variant = E.maximum(0, target.auto_star_deficits.get("HBD_fuel", 0)) var available: Variant = E.minimum(target.auto_resources.get("H", 0), world.ledger.available(target, "H")) quota = E.minimum(quota, E.divide((E.add(available, fuel)), cost)) diff --git a/world/owniverse/civilization_state.gd b/world/owniverse/civilization_state.gd index 4b010b4..faa9c83 100644 --- a/world/owniverse/civilization_state.gd +++ b/world/owniverse/civilization_state.gd @@ -231,4 +231,4 @@ static func effective_span(world, target: Dictionary, id: String, rules: Diction var weighted: Variant = population for home in target.civilization_homes.get(id, {}): weighted = E.add(weighted, E.multiply(target.civilization_homes[id][home], (E.subtract(E.maximum(0, world.catalog.entities[home].civ_bonus), 1)))) - return (E.divide(E.multiply(base, population), weighted) if E.gt(weighted, 0) else base) + return (E.mul_div(base, population, weighted) if E.gt(weighted, 0) else base) diff --git a/world/owniverse/item_description.gd b/world/owniverse/item_description.gd index 7bfec4b..3128e29 100644 --- a/world/owniverse/item_description.gd +++ b/world/owniverse/item_description.gd @@ -66,7 +66,7 @@ static func variables(world, global: Node, id: String, rate: Variant = 0.0) -> D var h: Variant = world.state.amount.get("H", 0) var mixture: String = "—" if E.gt(E.add(he, h), 0): - mixture = E.add(E.add(E.add(E.add("He: ", number(E.divide(E.multiply(100, he), (E.add(he, h))), 2)), "% / H: "), number(E.divide(E.multiply(100, h), (E.add(he, h))), 2)), "%") + mixture = E.add(E.add(E.add(E.add("He: ", number(E.mul_div(100, he, (E.add(he, h))), 2)), "% / H: "), number(E.mul_div(100, h, (E.add(he, h))), 2)), "%") var environment: Dictionary = world.environment.stellar_environment(world.state) return {"amount": amount, "colored costs": costs(world, global, id), "lifespan": (number(world.catalog.entities[id].lifespan) if world.catalog.entities.has(id) else "—"), "voids": number(voids), "he / h": E.add("\n", mixture), "metallicity": E.add(number(E.multiply(environment.metallicity, 100), 2), "%"), "энергия": number(world.state.energy), "плотность": number(environment.density, 6), "лет в секунду": number(world.state.speed_in_year), "amount1": "—", "amount2": "—", "amount3": "—", "amount4": "—"} diff --git a/world/owniverse/life_evolution.gd b/world/owniverse/life_evolution.gd index cf921fc..47fb8af 100644 --- a/world/owniverse/life_evolution.gd +++ b/world/owniverse/life_evolution.gd @@ -13,7 +13,7 @@ func life_products(target: Dictionary, entity: OwniverseEntity, count: Variant) target.life_remainders = {} for channel in ["progress", "degradation"]: var key: String = E.add(E.add(entity.id, ":"), channel) - var expected: Variant = E.add(E.divide(E.multiply(count, probabilities[channel]), probability_sum), target.life_remainders.get(key, 0)) + var expected: Variant = E.add(E.mul_div(count, probabilities[channel], probability_sum), target.life_remainders.get(key, 0)) target.life_remainders[key] = expected pending[channel] = E.floor_value(expected) var assigned: Dictionary = QuantityMath.allocate_whole(pending, count) @@ -31,7 +31,7 @@ func life_products(target: Dictionary, entity: OwniverseEntity, count: Variant) var requests: Dictionary = {} for id in weights: var key: String = E.add(E.add(entity.id, ":fall:"), id) - var expected: Variant = E.add(E.divide(E.multiply(assigned.degradation, weights[id]), weight_sum), target.life_remainders.get(key, 0)) + var expected: Variant = E.add(E.mul_div(assigned.degradation, weights[id], weight_sum), target.life_remainders.get(key, 0)) target.life_remainders[key] = expected requests[id] = E.maximum(0, E.floor_value(expected)) var falls: Dictionary = QuantityMath.allocate_whole(requests, assigned.degradation) diff --git a/world/owniverse/manual_production.gd b/world/owniverse/manual_production.gd index 278b18b..bd685b3 100644 --- a/world/owniverse/manual_production.gd +++ b/world/owniverse/manual_production.gd @@ -74,7 +74,7 @@ func plan_smbh(desired_amount: Variant) -> Dictionary: var order: Array = [] var consumed_mass: Variant = 0 for id in distribution: - var expected: Variant = E.divide(E.multiply(requested, distribution[id]), world.catalog.entities[id].mass) + var expected: Variant = E.mul_div(requested, distribution[id], world.catalog.entities[id].mass) taken[id] = E.minimum(E.floor_value(expected), E.floor_value(world.state.amount[id])) consumed_mass = E.add(consumed_mass, E.multiply(taken[id], world.catalog.entities[id].mass)) order.append({"id": id, "remainder": E.subtract(expected, taken[id])}) @@ -173,9 +173,9 @@ func production_step(entity: OwniverseEntity) -> Variant: return QuantityMath.production_step(E.add(entity.cost.values(), entity.bind.values())) -func calculate_max_producible(entity: OwniverseEntity, desired_amount: Variant = 1.0E150) -> Variant: +func calculate_max_producible(entity: OwniverseEntity, desired_amount: Variant = null) -> Variant: if E.eq(entity.id, "SMBH"): - return plan_smbh(desired_amount).amount + return plan_smbh(smbh_available() if desired_amount == null else desired_amount).amount var requirements: Dictionary = {} for resource in entity.cost: requirements[resource] = E.maximum(0, entity.cost[resource]) @@ -189,7 +189,7 @@ func calculate_max_producible(entity: OwniverseEntity, desired_amount: Variant = # No recipe means unavailable for production, not unlimited free objects. if (not has_requirements): return 0 - var available: Variant = E.maximum(0, desired_amount) + var available: Variant = INF if desired_amount == null else E.maximum(0, desired_amount) for resource in requirements: if E.gt(requirements[resource], 0): available = E.minimum(available, E.floor_value(E.divide(world.ledger.available(world.state, resource), requirements[resource]))) diff --git a/world/owniverse/math/README.md b/world/owniverse/math/README.md index 14ed7b4..ee7a4b9 100644 --- a/world/owniverse/math/README.md +++ b/world/owniverse/math/README.md @@ -1,82 +1,96 @@ # Числа симуляции -Количество объектов, запасы, энергия, расходы, привязки и статистика считаются -целочисленно, без перехода через `float`. Значения до `10^27` сохраняют каждую -единицу; промежуточные произведения могут превышать этот диапазон. +`exact.gd` сохраняет единый API `E` для симуляции и интерфейса. Целые числа, +помещающиеся в `int64`, остаются обычными `int`. Остальные значения хранятся +в `exact_value.gd`: -`big_integer.gd` хранит модуль числа массивом разрядов по основанию `10^9`, -младший разряд первым. Переносы при умножении не переполняют `int64`. -Деление использует нормализованную оценку цифры частного. +``` +polarity × (high × 10^14 + low + fraction) +``` -`exact.gd` — единый API арифметики. Малые значения остаются обычными `int`, -большие числа и дроби представлены `exact_value.gd`: знак, целый числитель, -целый знаменатель. Дроби сокращаются. Операции создают новые значения; -поля `ExactValue` и массивы его разрядов нельзя изменять после создания. +`high` и `low` — целые блоки от 0 до 10^14−1, `fraction` — `float` от 0 до 1, +`polarity` — знак. Значения неизменяемы после создания. Диапазон включает весь +порядок E27: модуль целой части меньше 10^28. Каждая единица целой части +сохраняется, в том числе при добавлении и вычитании единицы около 10^27. +Выход за диапазон проверяется утверждениями; насыщения или обрезания запасов нет. + +Числители и знаменатели в состоянии больше не хранятся. НОД при обычных +арифметических операциях не вычисляется; обход состояния для ограничения дробей +больше не нужен. `bounded` и `bound_state` оставлены как совместимые пустые операции. + +## Операции + +Сложение, вычитание и сравнение работают непосредственно с блоками. +Для умножения и деления `big_integer.gd` используется как временный вычислитель +с безопасными разрядами по основанию 10^9. Результат сразу возвращается в +фиксированную структуру. Размер временных дробей определяется входными float, +а не растёт от шага к шагу. + +`E.mul_div(a, b, denominator)` вычисляет отношение за одну операцию. Это нужно +для распределений: произведение двух количеств может превышать E27, хотя итог +деления укладывается в диапазон. Промежуточное произведение не становится +значением симуляции. Целое частное вычисляется до перевода остатка в float. ```gdscript const E = preload("res://world/owniverse/math/exact.gd") -var stock = E.of("1e27") # Строка, не литерал float 1e27! -stock = E.subtract(stock, 1) # 999999999999999999999999999 -var price = E.of("0.125") # Точно 1/8 -var cost = E.multiply(price, 8) # Точно 1 -if E.ge(stock, cost): - stock = E.subtract(stock, cost) +var stock = E.of("1e27") # Большое целое вводится строкой. +stock = E.subtract(stock, 1) +var share = E.mul_div(stock, 15, 100) # Точные 15% целого запаса. +var whole_share = E.floor_value(share) ``` Использовать `E.add/subtract/multiply/divide`, `E.eq/lt/le/gt/ge`, -`E.floor_value/ceil_value/round_value`. Перегрузки операторов в GDScript нет: -обычные `+`, `==`, `min`, `float(...)` для чисел симуляции не подходят. -Сигнатуры принимают `Variant`, а не `float`. `E.integer` допускается только -для значений в диапазоне `int64`, например индексов и состояния генератора. -Для поиска чисел в массиве используется `E.contains`, поскольку сравнение -объектов по ссылке не является сравнением числовых значений. +`E.floor_value/ceil_value/round_value`. Обычные операторы над объектами +`ExactValue` не подходят. `E.integer` допускается только для значений int64; +`E.approx` — явный переход к приближённому float. -## Границы точности +## Дробная точность -- Десятичные параметры CSV разбираются из строк точно. Стоимости, проверка - доступности и размер целой производственной партии не округляются. -- Суммы распределений и переносы целых объектов сохраняются точно. -- Промежуточная рациональная арифметика точна. Перед публикацией следующего - состояния знаменатели сохранённых дробей ограничиваются `10^36`. - Если знаменатель больше, значение усекается к нулю с ошибкой меньше - `10^-36`. Целые числа эта операция не меняет. Это предотвращает рост - знаменателей при многократном накоплении меняющихся долей. Такая точность - относится к каждой операции ограничения, а не является обещанием нулевой - суммарной ошибки при бесконечном числе циклов. -- Логарифмы, корни, нецелые степени и нормальная случайная выборка остаются - приближёнными. Результаты трансцендентных функций переводятся обратно в - рациональные коэффициенты с 12 знаками после точки. Статистическая выборка - не обеспечивает равномерный выбор каждого отдельного целого около `10^27`; - последующий учёт выбранного количества и остатка точен. -- Возраст хранится в целых тиках (`10^9` тиков на год). Малый остаток кадрового - времени остаётся `float` и переносится между кадрами; большой возраст - никогда не складывается с малым приращением через `float`. -- Интерфейс преобразует в `float` только координаты индикаторов. Текст чисел, - доступное количество и значения кнопок формируются из точных чисел. +Дробная часть приближённая. Для временного умножения/деления она преобразуется +в десятичную дробь с 16 значащими цифрами; нормализация сохраняет значащие цифры +малых коэффициентов. Десятичные стоимости вроде 0.125 и 0.0000000000001 +сохраняют корректный размер производственной партии. НОД применяется только +при определении знаменателя стоимости в `production_step`. -`E.of(float)` сохраняет фактическое двоичное значение входного числа: он -не может восстановить единицы, уже потерянные предыдущими вычислениями. +При сложении двух ненулевых дробных частей перенос около целой границы +стабилизируется с допуском 10^-12. Это предотвращает задержку редких событий +из-за накопленной ошибки float. Допуск не применяется к целым блокам или +округлению частного при делении. Суммы распределённых целых объектов проверяются +строго, без допуска. + +При умножении большого количества на приближённый коэффициент ошибка самого +коэффициента усиливается: float-доля не обещает точности результата до единицы. +Известные точные отношения нужно передавать через `mul_div(count, numerator, +denominator)`, не вычисляя коэффициент заранее. + +Возраст хранится как целые годы и дробный остаток; в наносекундные тики +переводится только дробная часть года. Полный возраст больше не умножается на +10^9. Остаток кадрового времени переносится отдельно. Геометрический объём +пустот может превышать E27 из-за множителя 10^22: он вычисляется в float и +используется только для приближённых коэффициентов плотности и бластов. + +Форматирование не создаёт больших промежуточных значений симуляции: например, +округлённая подпись `1.00E+28` допустима для количества чуть меньше 10^28. ## Сохранение -Формат сохранения — **3**, версия состояния — **0.2.0**. На главном потоке -`E.encode` строит независимый снимок, затем существующий фоновый писатель -сохраняет его. Большие целые и дроби кодируются как `{"$exact": "число"}`; -для дробей используется строка `числитель/знаменатель`. Целые за пределами -точного диапазона JSON/float тоже кодируются строкой. Зашифрованный файл и -диагностический JSON используют один снимок. После проверки `E.decode` -восстанавливает числа, без загрузки сериализованных объектов GDScript. +Формат сохранения — **4**, версия состояния — **0.3.0**. Большие целые +кодируются как `{"$exact": "целое число"}`. Блоки с дробью кодируются как +`{"$fixed": ["high", "low", "float bytes in hex", sign]}`. Восемь байтов float +сохраняются без потери точности JSON, включая очень малые остатки. +Зашифрованный файл и диагностический JSON используют один снимок. -Миграции старой арифметики нет. Старый формат несовместим: существующий -механизм загрузки сохраняет его в `.incompatible.bak` и начинает новую игру. -Старое сохранение игрока не используется тестами и не изменялось при разработке. +Миграции нет. Сохранение прежнего формата при обычной загрузке архивируется +существующим механизмом `.incompatible.bak`; начинается новая игра. +Тесты не загружают и не перезаписывают рабочее сохранение игрока. ## Проверки `godot --headless --path Owniverse.GI --script res://tests/test_exact_math.gd` Независимый эталон `tests/fixtures/exact_oracle.json` получен через Python -`fractions.Fraction` (120 пар, seed 4321). Тест сравнивает строковые результаты -суммы, разности, произведения, деления и округления вниз. Дополнительно -проверяются границы `int64`, JSON, рецепты, распределения, жизнь при `10^27`, -единичный расход энергии, тик времени и 100 циклов с меняющимися долями звёзд. +`fractions.Fraction` (160 пар, seed 4321). Произведения находятся внутри E27; +дробные результаты сравниваются с абсолютным допуском 10^-12, целые — точно. +Дополнительно проверяются границы int64/E27, единичные изменения, точные +проценты, объединённое умножение с делением, JSON, дробные рецепты, +распределения, жизнь, энергия, время, форматирование и 100 циклов симуляции. diff --git a/world/owniverse/math/exact.gd b/world/owniverse/math/exact.gd index 16f281f..5de7443 100644 --- a/world/owniverse/math/exact.gd +++ b/world/owniverse/math/exact.gd @@ -1,56 +1,76 @@ extends RefCounted -## Exact signed rational arithmetic. Native small integers are the fast path. +## Fixed whole blocks and a float remainder. Native integers are the fast path. const Big = preload("res://world/owniverse/math/big_integer.gd") const Value = preload("res://world/owniverse/math/exact_value.gd") const SMALL: int = 1000000000 const MAX_INT: int = 9223372036854775807 const MIN_INT: int = -9223372036854775807 - 1 -const CARRY_SCALE: Array = [0, 0, 0, 0, 1] # 10^36 +const BLOCK: int = 100000000000000 # 10^14 +const LIMIT: Array = [0, 0, 0, 10] # 10^28, exclusive static func make(n: Array, d: Array = [1], polarity: int = 1): assert(d != [0], "Zero denominator") - if n == [0]: return 0 - if d != [1]: - var common: Array = Big.gcd(n, d) - n = Big.divmod(n, common)[0] - d = Big.divmod(d, common)[0] - return _make_reduced(n, d, polarity) + var qr: Array = Big.divmod(n, d) + var fraction: float = 0.0 + if qr[1] != [0]: + var numerator_text: String = Big.decimal(qr[1]) + var denominator_text: String = Big.decimal(d) + if denominator_text.length() < 300: + fraction = float(numerator_text) / float(denominator_text) + else: + var exponent: int = maxi(0, numerator_text.length() - 16) - maxi(0, denominator_text.length() - 16) + fraction = float(numerator_text.left(16)) / float(denominator_text.left(16)) + fraction *= pow(10.0, maxi(-308, exponent)) + fraction *= pow(10.0, mini(0, exponent + 308)) + # A remainder below one must not round up and create a whole object. + fraction = minf(fraction, 1.0 - pow(2.0, -53)) + return _from_whole(qr[0], fraction, polarity) -## Internal: operands already reduced; do not repeat Euclid's algorithm. -static func _make_reduced(n: Array, d: Array, polarity: int): - if n == [0]: return 0 - if d == [1] and (n.size() == 1 or (n.size() == 2 and n[1] < SMALL)): - return polarity * (n[0] + (n[1] * SMALL if n.size() == 2 else 0)) - if d == [1] and n.size() == 3: - var limit: Array = [854775807 if polarity > 0 else 854775808, 223372036, 9] - if Big.compare(n, limit) <= 0: - return (("-" if polarity < 0 else "") + Big.decimal(n)).to_int() +static func _from_whole(n: Array, fraction: float, polarity: int): + var text: String = Big.decimal(n) + assert(Big.compare(n, LIMIT) < 0, "Number exceeds E27 range") + var high: int = int(text.left(text.length() - 14)) if text.length() > 14 else 0 + var low: int = int(text.right(14)) + return _compose(high, low, fraction, polarity) + +static func _compose(high: int, low: int, fraction: float, polarity: int): + if fraction >= 1.0: + low += int(floorf(fraction)) + fraction -= floorf(fraction) + if fraction < 0.0: + low -= 1 + fraction += 1.0 + if low >= BLOCK: + high += low / BLOCK + low %= BLOCK + elif low < 0: + high -= 1 + low += BLOCK + assert(high >= 0 and high < BLOCK, "Number exceeds E27 range") + if fraction == 0.0 and high <= 92233: + var prefix: int = high * BLOCK + if low <= MAX_INT - prefix: return polarity * (prefix + low) + if polarity < 0 and low == MAX_INT - prefix + 1: return MIN_INT var result = Value.new() - result.numerator = n - result.denominator = d + result.high = high + result.low = low + result.fraction = fraction result.polarity = polarity return result +static func _blocks(value) -> Array: + value = of(value) + if value is Value: return [value.high, value.low, value.fraction, value.polarity] + # Signed division also handles MIN_INT without overflowing abs(). + return [absi(value / BLOCK), absi(value % BLOCK), 0.0, -1 if value < 0 else 1] + static func of(value): if value is Value or value is int: return value if value is float: if not is_finite(value): return value if value == floorf(value) and absf(value) < 1e18: return int(value) - # Imports the actual binary float, not an invented decimal rounding. - var data := PackedByteArray() - data.resize(8) - data.encode_double(0, value) - var bits: int = data.decode_s64(0) - var exponent: int = (bits >> 52) & 2047 - var mantissa: int = bits & 4503599627370495 - if exponent: mantissa += 4503599627370496 - var power: int = exponent - 1075 if exponent else -1074 - var n: Array = Big.parse(str(mantissa)) - var d: Array = [1] - for i in absi(power): - if power > 0: n = Big.multiply(n, [2]) - else: d = Big.multiply(d, [2]) - return make(n, d, -1 if value < 0 else 1) + var magnitude: float = absf(value) + return _from_whole(Big.parse("%.0f" % floorf(magnitude)), magnitude - floorf(magnitude), -1 if value < 0 else 1) var text: String = str(value).strip_edges().to_lower() if text == "": return 0 if "/" in text: @@ -72,7 +92,17 @@ static func of(value): static func parts(value) -> Array: value = of(value) if value is int: return [Big.parse(str(value).trim_prefix("-")), [1], -1 if value < 0 else 1] - return [value.numerator, value.denominator, value.polarity] + var whole: Array = Big.parse(str(value.high) + "%014d" % value.low) + if value.fraction == 0.0: return [whole, [1], value.polarity] + # Bound temporary decimal work to 16 significant digits of the float. + # Normalize first so tiny rates retain their significant digits as well. + var exponent: int = maxi(-323, int(floor(log(value.fraction) / log(10.0)))) + var text: String = String.num(value.fraction / pow(10.0, exponent), 15) + var point: int = text.find(".") + if point >= 0: exponent -= text.length() - point - 1 + var n: Array = Big.parse(text.replace(".", "")) + var d: Array = Big.parse("1" + "0".repeat(-exponent)) + return [Big.add(Big.multiply(whole, d), n), d, value.polarity] static func numeric(value) -> bool: return value is int or value is float or value is Value @@ -82,9 +112,7 @@ static func finite(value) -> bool: static func approx(value) -> float: if not value is Value: return float(value) - var a: String = Big.decimal(value.numerator) - var b: String = Big.decimal(value.denominator) - return value.polarity * float(a.left(16)) / float(b.left(16)) * pow(10.0, maxi(0, a.length() - 16) - maxi(0, b.length() - 16)) + return value.polarity * (float(value.high) * BLOCK + float(value.low) + value.fraction) static func compare(a, b) -> int: if a is int and b is int: return 0 if a == b else (1 if a > b else -1) @@ -92,10 +120,12 @@ static func compare(a, b) -> int: var x: float = approx(a) var y: float = approx(b) return 0 if x == y else (1 if x > y else -1) - var x: Array = parts(a) - var y: Array = parts(b) - if x[2] != y[2]: return 1 if x[2] > y[2] else -1 - return x[2] * Big.compare(Big.multiply(x[0], y[1]), Big.multiply(y[0], x[1])) + var x: Array = _blocks(a) + var y: Array = _blocks(b) + if x[3] != y[3]: return 1 if x[3] > y[3] else -1 + for i in 3: + if x[i] != y[i]: return x[3] * (1 if x[i] > y[i] else -1) + return 0 static func eq(a, b) -> bool: if a == null: return b == null @@ -134,69 +164,68 @@ static func add(a, 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) - if a is int and a == 0: return of(b) - if b is int and b == 0: return of(a) - var x: Array = parts(a) - var y: Array = parts(b) - var common: Array = Big.gcd(x[1], y[1]) - var xd: Array = Big.divmod(x[1], common)[0] - var yd: Array = Big.divmod(y[1], common)[0] - var left: Array = Big.multiply(x[0], yd) - var right: Array = Big.multiply(y[0], xd) - var denominator: Array = Big.multiply(xd, y[1]) - var numerator: Array - var polarity: int = x[2] - if x[2] == y[2]: - numerator = Big.add(left, right) - else: - var order: int = Big.compare(left, right) - numerator = Big.subtract(left, right) if order >= 0 else Big.subtract(right, left) - polarity = x[2] if order >= 0 else y[2] - # Reduced inputs can only share a remaining factor from gcd(d1, d2). - # Testing the entire new denominator repeats expensive long division. - var reduction: Array = Big.gcd(numerator, common) - return _make_reduced(Big.divmod(numerator, reduction)[0], Big.divmod(denominator, reduction)[0], polarity) + var x: Array = _blocks(a) + var y: Array = _blocks(b) + if x[3] == y[3]: + var fraction: float = x[2] + y[2] + # Repeated fractional accumulation may land just below an integer. + # This tolerance only applies to the fractional carry, never whole blocks. + if x[2] > 0.0 and y[2] > 0.0 and fraction > 0.5 and absf(fraction - roundf(fraction)) <= 1e-12: + fraction = roundf(fraction) + return _compose(x[0] + y[0], x[1] + y[1], fraction, x[3]) + var order: int = 0 + for i in 3: + if x[i] != y[i]: + order = 1 if x[i] > y[i] else -1 + break + if order == 0: return 0 + if order < 0: + var tmp: Array = x + x = y + y = tmp + return _compose(x[0] - y[0], x[1] - y[1], x[2] - y[2], x[3]) static func subtract(a, b): if not numeric(a) or not numeric(b): return a - b 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 if not finite(a) or not finite(b): return approx(a) - approx(b) - if b is int and b == 0: return of(a) - var y: Array = parts(b) - return add(a, _make_reduced(y[0], y[1], -y[2])) + var y: Array = _blocks(b) + return add(a, _compose(y[0], y[1], y[2], -y[3])) static func multiply(a, b): if not numeric(a) or not numeric(b): return a * b if not finite(a) or not finite(b): return approx(a) * approx(b) - if (a is int and a == 0) or (b is int and b == 0): return 0 - if a is int and a == 1: return of(b) - if b is int and b == 1: return of(a) + if eq(a, 0) or eq(b, 0): return 0 + if eq(a, 1): return of(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 var x: Array = parts(a) var y: Array = parts(b) - var g1: Array = Big.gcd(x[0], y[1]) - var g2: Array = Big.gcd(y[0], x[1]) - return _make_reduced(Big.multiply(Big.divmod(x[0], g1)[0], Big.divmod(y[0], g2)[0]), Big.multiply(Big.divmod(x[1], g2)[0], Big.divmod(y[1], g1)[0]), x[2] * y[2]) + return make(Big.multiply(x[0], y[0]), Big.multiply(x[1], y[1]), x[2] * y[2]) + +## Fused ratio: the temporary product is never stored as a simulation value. +static func mul_div(a, b, divisor): + if not finite(a) or not finite(b) or not finite(divisor): return of(approx(a) * approx(b) / approx(divisor)) + var x: Array = parts(a) + var y: Array = parts(b) + var z: Array = parts(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]) static func divide(a, b): if not numeric(a) or not numeric(b): return a / b if not finite(a) or not finite(b): return approx(a) / approx(b) - if b is int and b == 1: return of(a) + if eq(b, 1): return of(a) if a is int and b is int and b != 0 and not (a == MIN_INT and b == -1): if a % b == 0: return a / b - var y: Array = parts(b) - assert(y[0] != [0], "Exact division by zero") - return multiply(a, _make_reduced(y[1], y[0], y[2])) + return mul_div(a, 1, b) static func floor_value(a): - if not finite(a): return a - if a is int: return a - var x: Array = parts(a) - var qr: Array = Big.divmod(x[0], x[1]) - if x[2] < 0 and qr[1] != [0]: qr[0] = Big.add(qr[0], [1]) - return make(qr[0], [1], x[2]) + if not finite(a) or a is int: return a + var x: Array = _blocks(a) + return _compose(x[0], x[1] + (1 if x[3] < 0 and x[2] > 0 else 0), 0.0, x[3]) static func ceil_value(a): return neg(floor_value(neg(a))) static func round_value(a): return multiply(-1 if lt(a, 0) else 1, floor_value(add(absolute(a), of("0.5")))) @@ -207,24 +236,13 @@ static func integer(a) -> int: assert(result is int, "Value does not fit native int64") return result -## Bound persistent fractions, never integer quantities or recipe arithmetic. -## Truncation towards zero changes a value by less than 10^-36. -static func bounded(value): - if not value is Value or Big.compare(value.denominator, CARRY_SCALE) <= 0: - return value - var units: Array = Big.divmod(Big.multiply(value.numerator, CARRY_SCALE), value.denominator)[0] - return make(units, CARRY_SCALE, value.polarity) +## Kept as no-op API: float remainder storage is already bounded. +static func bounded(value): return value -## Only call on a newly owned state snapshot, never on a published old state. -static func bound_state(value): - if value is Value: return bounded(value) - if value is Dictionary: - for key in value: value[key] = bound_state(value[key]) - elif value is Array: - for i in value.size(): value[i] = bound_state(value[i]) - return value +static func bound_state(value): return value static func power(a, b): + if finite(b) and lt(b, 0): return of(pow(approx(a), approx(b))) if finite(b) and eq(b, floor_value(b)) and le(absolute(b), 10000): var exponent: int = integer(absolute(b)) var result = 1 @@ -232,7 +250,7 @@ static func power(a, b): if exponent % 2: result = multiply(result, a) exponent /= 2 if exponent: a = multiply(a, a) - return divide(1, result) if lt(b, 0) else result + return result return coefficient(pow(approx(a), approx(b))) static func coefficient(value: float): @@ -253,21 +271,27 @@ static func contains(container, value) -> bool: return value in container static func fixed(value, digits: int = 0) -> String: - var scale = power(10, digits) - var rounded = round_value(multiply(value, scale)) - var text: String = str(absolute(rounded)) + if not finite(value): return str(value) + var x: Array = parts(value) + var scale: Array = Big.parse("1" + "0".repeat(digits)) + var qr: Array = Big.divmod(Big.multiply(x[0], scale), x[1]) + if Big.compare(Big.multiply(qr[1], [2]), x[1]) >= 0: qr[0] = Big.add(qr[0], [1]) + var text: String = Big.decimal(qr[0]) if digits: text = text.lpad(digits + 1, "0") text = text.left(text.length() - digits) + "." + text.right(digits) - return ("-" if lt(rounded, 0) else "") + text + return ("-" if x[2] < 0 and qr[0] != [0] else "") + text static func scientific(value, digits: int = 4) -> String: if eq(value, 0): return fixed(0, digits) + "E+0" var terms: Array = parts(absolute(value)) var exponent: int = Big.decimal(terms[0]).length() - Big.decimal(terms[1]).length() - if lt(absolute(value), power(10, exponent)): exponent -= 1 - var mantissa = divide(value, power(10, exponent)) - if ge(absolute(round_value(multiply(mantissa, power(10, digits)))), multiply(10, power(10, digits))): + var scale = of("1e%d" % exponent) + if lt(absolute(value), scale): + exponent -= 1 + scale = of("1e%d" % exponent) + var mantissa = divide(value, scale) + if float(fixed(absolute(mantissa), digits)) >= 10.0: mantissa = divide(mantissa, 10) exponent += 1 return fixed(mantissa, digits) + ("E+%d" if exponent >= 0 else "E%d") % exponent @@ -285,7 +309,11 @@ static func grouped(value, digits: int = 0) -> String: return ("-" if lt(value, 0) else "") + integer_text + result + ("." + parts_text[1] if parts_text.size() > 1 else "") static func encode(value): - if value is Value: return {"$exact": str(value)} + if value is Value: + var bytes := PackedByteArray() + bytes.resize(8) + bytes.encode_double(0, value.fraction) + return {"$fixed": [str(value.high), str(value.low), bytes.hex_encode(), value.polarity]} if value is int and (value > 9007199254740991 or value < -9007199254740991): return {"$exact": str(value)} if value is Dictionary: @@ -300,6 +328,9 @@ static func encode(value): static func decode(value): if value is Dictionary: + if value.size() == 1 and value.has("$fixed"): + var fields: Array = value["$fixed"] + return _compose(int(fields[0]), int(fields[1]), fields[2].hex_decode().decode_double(0), int(fields[3])) if value.size() == 1 and value.has("$exact"): return of(value["$exact"]) var result: Dictionary = {} for k in value: result[k] = decode(value[k]) @@ -311,22 +342,29 @@ static func decode(value): return value static func valid_encoded(value) -> bool: + if value is float: return is_finite(value) and absf(value) < 1e28 if value is Dictionary: + if value.has("$fixed"): + if value.size() != 1 or not value["$fixed"] is Array: return false + var fields: Array = value["$fixed"] + if fields.size() != 4: return false + for i in 2: + if not fields[i] is String or not fields[i].is_valid_int() or fields[i].length() > 14: return false + if int(fields[i]) < 0 or int(fields[i]) >= BLOCK: return false + if not fields[2] is String or fields[2].length() != 16 or not fields[2].is_valid_hex_number(): return false + var fraction: float = fields[2].hex_decode().decode_double(0) + return is_finite(fraction) and fraction >= 0.0 and fraction < 1.0 and fields[3] in [-1, 1] if value.has("$exact"): if value.size() != 1 or not value["$exact"] is String: return false var text: String = value["$exact"] - if text.length() > 4096: return false + if text.length() > 29: return false var terms: PackedStringArray = text.split("/") - if terms.size() > 2: return false + if terms.size() != 1: return false var n: String = terms[0].trim_prefix("-") if n.is_empty(): return false for ch in n: if ch < "0" or ch > "9": return false - if terms.size() == 2: - if terms[1].is_empty(): return false - for ch in terms[1]: - if ch < "0" or ch > "9": return false - if Big.parse(terms[1]) == [0]: return false + if Big.compare(Big.parse(n), LIMIT) >= 0: return false return true for k in value: if not valid_encoded(value[k]): return false diff --git a/world/owniverse/math/exact_value.gd b/world/owniverse/math/exact_value.gd index 33a8a46..388e39f 100644 --- a/world/owniverse/math/exact_value.gd +++ b/world/owniverse/math/exact_value.gd @@ -1,9 +1,11 @@ extends RefCounted -## Immutable by contract; arithmetic creates new values. Signed reduced fraction. +## Immutable magnitude high * 10^14 + low + fraction, with a separate sign. var polarity: int = 1 -var numerator: Array = [0] -var denominator: Array = [1] +var high: int = 0 +var low: int = 0 +var fraction: float = 0.0 func _to_string() -> String: - const Big = preload("res://world/owniverse/math/big_integer.gd") - return ("-" if polarity < 0 else "") + Big.decimal(numerator) + ("/" + Big.decimal(denominator) if denominator != [1] else "") + var whole: String = str(high) + "%014d" % low if high else str(low) + var tail: String = String.num(fraction, 16).trim_prefix("0").trim_suffix(".") if fraction else "" + return ("-" if polarity < 0 else "") + whole + tail diff --git a/world/owniverse/object_batches.gd b/world/owniverse/object_batches.gd index 66a9f28..36199af 100644 --- a/world/owniverse/object_batches.gd +++ b/world/owniverse/object_batches.gd @@ -66,7 +66,7 @@ func append_batch(target: Dictionary, id: String, amount: Variant, created_from: # next integer. Skip expired empty windows without changing the span # passed to the next generation or the lifetime production integral. if skip_empty_expired: - var threshold = E.add(created_from, E.divide(E.multiply(duration, E.add(E.subtract(amount, remaining_amount), 1)), amount)) + var threshold = E.add(created_from, E.mul_div(duration, E.add(E.subtract(amount, remaining_amount), 1), amount)) var next_nonempty = E.subtract(E.ceil_value(E.divide(E.subtract(threshold, origin), batch_duration)), 1) var expired_end = E.floor_value(E.divide(E.subtract(E.subtract(completed_before, lifespan), origin), batch_duration)) var skip_to = E.minimum(next_nonempty, expired_end) @@ -83,7 +83,7 @@ func append_batch(target: Dictionary, id: String, amount: Variant, created_from: var part_end: Variant = E.minimum(created_to, window_end) var part_amount: Variant = remaining_amount if (E.gt(duration, 0) and E.lt(part_end, created_to)): - part_amount = E.subtract(E.floor_value(E.divide(E.multiply(amount, (E.subtract(part_end, created_from))), duration)), (E.subtract(amount, remaining_amount))) + part_amount = E.subtract(E.floor_value(E.mul_div(amount, (E.subtract(part_end, created_from)), duration)), (E.subtract(amount, remaining_amount))) if E.le(E.add(window_end, lifespan), completed_before): # Retain the original integer allocation and lifetime integral, but # do not allocate thousands of already-dead batch dictionaries. @@ -132,7 +132,7 @@ func withdraw_batches(target: Dictionary, id: String, to_year: Variant) -> Array var until: Variant = E.minimum(to_year, end) if E.lt(to_year, end): var carry: Variant = batch.get("withdraw_remainder", 0) - var expected: Variant = E.add(carry, E.divide(E.multiply((E.subtract(amount, carry)), (E.subtract(to_year, start))), (E.subtract(end, start)))) + var expected: Variant = E.add(carry, E.mul_div((E.subtract(amount, carry)), (E.subtract(to_year, start)), (E.subtract(end, start)))) consumed = E.floor_value(expected) batch.withdraw_remainder = E.subtract(expected, consumed) batch.amount = E.maximum(0, E.subtract(amount, consumed)) @@ -160,7 +160,7 @@ func expired_years(year: Variant, start: Variant, end: Variant) -> Variant: return E.subtract(year, start) if E.ge(year, end): return E.add(E.subtract(year, end), E.multiply((E.subtract(end, start)), E.of("0.5"))) - return E.divide(E.multiply((E.subtract(year, start)), (E.subtract(year, start))), (E.multiply(2, (E.subtract(end, start))))) + return E.mul_div((E.subtract(year, start)), (E.subtract(year, start)), (E.multiply(2, (E.subtract(end, start))))) func consume_from_batches(entity: OwniverseEntity, current_year: Variant) -> Variant: diff --git a/world/owniverse/owniverse.gd b/world/owniverse/owniverse.gd index d83c63b..94222bb 100644 --- a/world/owniverse/owniverse.gd +++ b/world/owniverse/owniverse.gd @@ -52,7 +52,7 @@ var speed: int : state.speed_in_year = E.power(10, E.subtract(state.speed, 1)) func evolute(delta: Variant) -> void: - if (E.le(delta, 0) or (not E.finite(delta))): + if not E.finite(delta) or E.approx(delta) <= 0.0 or E.approx(delta) * E.approx(state.speed_in_year) >= 1e27: return var clock: Dictionary = SimulationTime.advance(state.time, E.approx(delta), speed) if not E.finite(clock.datetime): return diff --git a/world/owniverse/quantity_math.gd b/world/owniverse/quantity_math.gd index 279ff05..0844074 100644 --- a/world/owniverse/quantity_math.gd +++ b/world/owniverse/quantity_math.gd @@ -20,7 +20,7 @@ static func allocate_whole(requests: Dictionary, limit: Variant) -> Dictionary: var order: Array = [] var assigned: Variant = 0 for id in requests: - var expected: Variant = (E.divide(E.multiply(E.floor_value(E.maximum(0, requests[id])), available), total) if E.gt(total, 0) else 0) + var expected: Variant = (E.mul_div(E.floor_value(E.maximum(0, requests[id])), available, total) if E.gt(total, 0) else 0) result[id] = E.floor_value(expected) assigned = E.add(assigned, result[id]) order.append({"id": id, "rest": E.subtract(expected, result[id])}) @@ -38,7 +38,8 @@ static func production_step(costs: Array): var step = 1 for cost in costs: if E.le(cost, 0): continue - var denominator = E.make(E.parts(cost)[1]) + var terms: Array = E.parts(cost) + var denominator = E.make(E.Big.divmod(terms[1], E.Big.gcd(terms[0], terms[1]))[0]) step = E.multiply(E.divide(step, whole_gcd(step, denominator)), denominator) return step @@ -76,12 +77,12 @@ static func split(remainders: Dictionary, key: String, count: Variant, weights: var expected: Dictionary = {} var positive_sum: Variant = 0 for id in weights: - expected[id] = E.add(E.divide(E.multiply(count, weights[id]), sum_weights), remainders.get(E.add(E.add(key, ":"), id), 0)) + expected[id] = E.add(E.mul_div(count, weights[id], sum_weights), remainders.get(E.add(E.add(key, ":"), id), 0)) positive_sum = E.add(positive_sum, E.maximum(0, expected[id])) var order: Array = [] var assigned: Variant = 0 for id in weights: - var normalized: Variant = (E.divide(E.multiply(E.maximum(0, expected[id]), count), positive_sum) if E.gt(positive_sum, 0) else 0) + var normalized: Variant = (E.mul_div(E.maximum(0, expected[id]), count, positive_sum) if E.gt(positive_sum, 0) else 0) result[id] = E.floor_value(normalized) assigned = E.add(assigned, result[id]) order.append({"id": id, "rest": E.subtract(normalized, result[id])}) diff --git a/world/owniverse/simulation_time.gd b/world/owniverse/simulation_time.gd index fff6dbd..691efcb 100644 --- a/world/owniverse/simulation_time.gd +++ b/world/owniverse/simulation_time.gd @@ -1,19 +1,24 @@ extends RefCounted const E = preload("res://world/owniverse/math/exact.gd") -const STATE_VERSION: String = "0.2.0" +const STATE_VERSION: String = "0.3.0" const TICKS_PER_YEAR: int = 1000000000 static func processed_year(datetime): - return E.divide(E.floor_value(E.multiply(datetime, 10)), 10) + var whole = E.floor_value(datetime) + var ticks: int = roundi(E.approx(E.subtract(datetime, whole)) * TICKS_PER_YEAR) + return E.add(whole, E.divide(ticks / 100000000, 10)) static func elapsed_years(from_year, to_year): return E.subtract(to_year, from_year) -## Frame fractions stay small; the universe age is an exact integer tick count. +## Only the fractional year is converted to ticks; the whole age stays exact. static func advance(time: Dictionary, delta: float, speed: int) -> Dictionary: - var ticks = E.round_value(E.multiply(time.datetime, TICKS_PER_YEAR)) - var increment: float = delta * pow(10.0, speed - 1) * TICKS_PER_YEAR + float(time.get("tick_remainder", 0.0)) + var whole_years = E.floor_value(time.datetime) + var ticks: int = roundi(E.approx(E.subtract(time.datetime, whole_years)) * TICKS_PER_YEAR) + var elapsed_years_float: float = delta * pow(10.0, speed - 1) + var elapsed_whole = E.of(floorf(elapsed_years_float)) + var increment: float = (elapsed_years_float - floorf(elapsed_years_float)) * TICKS_PER_YEAR + float(time.get("tick_remainder", 0.0)) var whole: float = roundf(increment) if absf(increment - roundf(increment)) < 1e-6 else floorf(increment) - ticks = E.add(ticks, E.of(whole)) - var datetime = E.divide(ticks, TICKS_PER_YEAR) - return {"datetime": datetime, "year": processed_year(datetime), "ticks": ticks, "tick_remainder": increment - whole} + var elapsed = E.add(elapsed_whole, E.divide(E.of(whole), TICKS_PER_YEAR)) + var datetime = E.add(whole_years, E.add(E.divide(ticks, TICKS_PER_YEAR), elapsed)) + return {"datetime": datetime, "year": processed_year(datetime), "tick_remainder": increment - whole} diff --git a/world/owniverse/stellar_rules.gd b/world/owniverse/stellar_rules.gd index 04e4ed4..3d397ba 100644 --- a/world/owniverse/stellar_rules.gd +++ b/world/owniverse/stellar_rules.gd @@ -102,7 +102,7 @@ func apply_blast(target: Dictionary, power: Variant, environment: Dictionary) -> var distribution: Dictionary = QuantityMath.normalize_distribution(weights) # Fractional exposure is deterministic; destruction itself remains whole. for id in distribution: - destroy_planets(target, id, E.multiply(E.divide(E.multiply(hits, distribution[id]), quantities[id]), environment.density), 1, "blast") + destroy_planets(target, id, E.multiply(E.mul_div(hits, distribution[id], quantities[id]), environment.density), 1, "blast") func uses_short_lived_fast_path(id: String) -> bool: diff --git a/world/owniverse/universe_environment.gd b/world/owniverse/universe_environment.gd index 063cb6c..cda315c 100644 --- a/world/owniverse/universe_environment.gd +++ b/world/owniverse/universe_environment.gd @@ -6,20 +6,22 @@ func stellar_environment(source: Dictionary) -> Dictionary: var quantities: Dictionary = world.ledger.physical_amounts(source) # SMBH is a self-group in GI; its quantity stores mass, not object count. var mass: Variant = source.amount.get("SMBH", 0) - var volume: Variant = quantities.get("S", 0) + # Geometric volume includes a 1e22 scale per void and can exceed E27. + # It only feeds approximate density/blast coefficients, never stock counts. + var volume: float = E.approx(quantities.get("S", 0)) for id in quantities: var entity: OwniverseEntity = world.catalog.entities[id] if E.contains(["H", "He", "Met", "SMBH"], id): mass = E.add(mass, quantities[id]) else: mass = E.add(mass, E.multiply(quantities[id], E.maximum(0, entity.mass))) - volume = E.add(volume, E.multiply(quantities[id], E.maximum(0, entity.space))) - volume = E.add(volume, E.multiply(E.multiply(quantities[id], E.maximum(0, entity.voids)), E.of("1.0e22"))) + 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 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.divide(E.multiply(100, source.amount.get("Met", 0)), mass), E.of("0.11")))), E.of("0.88"))) + 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))))) - return {"metallicity": metallicity, "density": E.divide(E.multiply(E.of("1.23456e-7"), E.square_root(E.add(base_mass, mass))), E.power(E.maximum(1, volume), E.divide(1, 6))), "blast": (E.clamp_value(E.divide(source.amount.get("S", 0), volume), 0, 1) if E.gt(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} func modify_world_parameter(key: String, change: Variant) -> void: diff --git a/world/owniverse/world_parameters.gd b/world/owniverse/world_parameters.gd index de42bec..7e7a203 100644 --- a/world/owniverse/world_parameters.gd +++ b/world/owniverse/world_parameters.gd @@ -91,7 +91,7 @@ static func _integration(target: Dictionary, events: Variant) -> void: var chosen: int = rng.randi_range(0, 2) counts[chosen] = E.add(counts[chosen], 1) else: - counts[0] = E.clamp_value(E.round_value(E.of(rng.randfn(E.approx(E.divide(events, 3)), E.approx(E.square_root(E.divide(E.multiply(events, 2), 9)))))), 0, events) + counts[0] = E.clamp_value(E.round_value(E.of(rng.randfn(E.approx(E.divide(events, 3)), E.approx(E.square_root(E.mul_div(events, 2, 9)))))), 0, events) var rest: Variant = E.subtract(events, counts[0]) counts[1] = E.clamp_value(E.round_value(E.of(rng.randfn(E.approx(E.divide(rest, 2)), E.approx(E.square_root(E.divide(rest, 4)))))), 0, rest) counts[2] = E.subtract(rest, counts[1]) diff --git a/world/world.tscn b/world/world.tscn index a3994fa..4d157d1 100644 --- a/world/world.tscn +++ b/world/world.tscn @@ -60,6 +60,7 @@ [ext_resource type="Texture2D" uid="uid://so06vth6le3b" path="res://ui/main_menu/ru/russian_active.png" id="51_uvuuk"] [ext_resource type="Texture2D" uid="uid://dwqpcm1pryw44" path="res://ui/main_menu/en/leave_reality.png" id="52_uvuuk"] [ext_resource type="Texture2D" uid="uid://btghvfdh3guo5" path="res://ui/report/en/report.png" id="60_an6vj"] +[ext_resource type="Texture2D" uid="uid://dvt8fw1m5mpki" path="res://ui/main_menu/sound/sound_runner.png" id="61_bemti"] [ext_resource type="Texture2D" uid="uid://bce2bpw5642er" path="res://ui/achievements/achievements.en.png" id="61_d36xh"] [ext_resource type="Script" uid="uid://vy0dj2cqfmk0" path="res://ui/credits/credits.gd" id="62_o2i0n"] [ext_resource type="Texture2D" uid="uid://c46h78ewwlafk" path="res://ui/credits/credits.en.png" id="62_vu83j"] @@ -986,11 +987,15 @@ theme_override_constants/separation = 64 [node name="SoundVolume" type="HSlider" parent="Stage/MainMenu/Volume" unique_id=1297986446] custom_minimum_size = Vector2(444, 96) layout_mode = 2 +theme_override_icons/grabber = ExtResource("61_bemti") +theme_override_icons/grabber_highlight = ExtResource("61_bemti") scrollable = false [node name="MusicVolume" type="HSlider" parent="Stage/MainMenu/Volume" unique_id=729455266] custom_minimum_size = Vector2(444, 96) layout_mode = 2 +theme_override_icons/grabber = ExtResource("61_bemti") +theme_override_icons/grabber_highlight = ExtResource("61_bemti") scrollable = false [node name="Credits" type="TextureButton" parent="Stage/MainMenu" unique_id=44726060]