Update speed limit, enable embedded PCK format, and enhance brown dwarf production logic
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const SPEED_LIMIT: int = 5
|
const SPEED_LIMIT: int = 6
|
||||||
|
|
||||||
# Write a readable diagnostic copy next to the encrypted save. Never used for loading.
|
# Write a readable diagnostic copy next to the encrypted save. Never used for loading.
|
||||||
var save_plaintext_copy: bool = true
|
var save_plaintext_copy: bool = true
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ script_export_mode=2
|
|||||||
custom_template/debug=""
|
custom_template/debug=""
|
||||||
custom_template/release=""
|
custom_template/release=""
|
||||||
debug/export_console_wrapper=1
|
debug/export_console_wrapper=1
|
||||||
binary_format/embed_pck=false
|
binary_format/embed_pck=true
|
||||||
texture_format/s3tc_bptc=true
|
texture_format/s3tc_bptc=true
|
||||||
texture_format/etc2_astc=false
|
texture_format/etc2_astc=false
|
||||||
shader_baker/enabled=false
|
shader_baker/enabled=false
|
||||||
|
|||||||
@@ -13,6 +13,18 @@
|
|||||||
7. Применить суммарный бласт поколения; повторять обработку поколений, пока в интервале остаются переходы.
|
7. Применить суммарный бласт поколения; повторять обработку поколений, пока в интервале остаются переходы.
|
||||||
8. Пересчитать группы и опубликовать состояние.
|
8. Пересчитать группы и опубликовать состояние.
|
||||||
|
|
||||||
|
## Автоматическое создание бурых карликов
|
||||||
|
|
||||||
|
На горячие бурые карлики `HBD` выделяется 0,1% **нового фактически произведённого автоматического водорода** за расчётный интервал. По текущей таблице автоматическую долю имеют STN (`auto = 0.5`) и ASTN (`auto = 0.65`). Обычный водород, ручные покупки и неиспользованный запас прошлых интервалов повторно в эту долю не входят.
|
||||||
|
|
||||||
|
При стоимости `c = 0.1 H` квота объектов увеличивается на `ΔQ = ΔH_auto × 0.001 / c`. Она хранится между циклами и в сохранении в `auto_star_deficits.HBD`. Доступность ограничивается автоматическим запасом и уже оплаченным топливом: `Q ≤ (auto_resources.H + HBD_fuel) / c`.
|
||||||
|
|
||||||
|
Карлики появляются по одному. Для `q = max(0, Q)` создаётся `floor(q)` объектов и ещё один с вероятностью `q - floor(q)`, с ограничением по доступному топливу. Созданное количество вычитается из Q; отрицательный остаток компенсирует досрочное появление на следующих шагах. Повторного случайного округления без нового автоматического водорода нет. Это исключение из общего детерминированного округления GI. Состояние генератора `auto_star_deficits.HBD_random` сохраняется, поэтому загрузка продолжает ту же последовательность.
|
||||||
|
|
||||||
|
Водород списывается целыми единицами авансом: `charge = ceil(max(0, count × c - HBD_fuel))`. Неиспользованный остаток `HBD_fuel += charge - count × c` сохраняется в `auto_star_deficits.HBD_fuel` и больше не входит в свободный запас или бюджет обычных звёзд. Например, 500 автоматических H позволяют создать 5 HBD, списав 1 H и оставив 0,5 H оплаченного топлива. Следующие 5 HBD используют этот остаток без повторного списания. Численность всегда целая, но не обязана быть кратной 10. При отсутствии обычных звёзд их доля остаётся в запасе, а производство HBD всё равно работает.
|
||||||
|
|
||||||
|
Новые HBD получают обычные жизненные партии. По таблице их срок — 5 миллиардов лет, затем они постепенно превращаются в холодные `CBD`. На экране оба типа суммируются в `BD`. Ручного рецепта у общего счётчика BD нет. Миграция сохранений не требуется: квота использует существующий словарь, отсутствующее значение равно нулю.
|
||||||
|
|
||||||
## Постепенные партии
|
## Постепенные партии
|
||||||
|
|
||||||
Окно рождения шириной `w = lifespan / 40` при `lifespan > 10`, иначе `w = lifespan`. Окно превращения сдвинуто на `lifespan`. При частичном списании:
|
Окно рождения шириной `w = lifespan / 40` при `lifespan > 10`, иначе `w = lifespan`. Окно превращения сдвинуто на `lifespan`. При частичном списании:
|
||||||
|
|||||||
+12
-64
@@ -9,10 +9,6 @@ var description_targets: Dictionary = {}
|
|||||||
var description_samples: Dictionary = {}
|
var description_samples: Dictionary = {}
|
||||||
var description_rates: Dictionary = {}
|
var description_rates: Dictionary = {}
|
||||||
var description_elapsed: float = 0.0
|
var description_elapsed: float = 0.0
|
||||||
const UI_UPDATE_INTERVAL: float = 0.1
|
|
||||||
var ui_elapsed: float = 0.0
|
|
||||||
var ui_pages: Vector2i = Vector2i(-1, -1)
|
|
||||||
var closing: bool = false
|
|
||||||
|
|
||||||
const PAGE_COUNT = 5
|
const PAGE_COUNT = 5
|
||||||
const RESOURCE_PAGE = 1
|
const RESOURCE_PAGE = 1
|
||||||
@@ -93,9 +89,7 @@ func set_datetime(value: float):
|
|||||||
# Simulation time advances in tenths of a year; displaying five decimal
|
# Simulation time advances in tenths of a year; displaying five decimal
|
||||||
# places only adds zeroes and suggests precision the simulation does not
|
# places only adds zeroes and suggests precision the simulation does not
|
||||||
# have.
|
# have.
|
||||||
var text: String = Global.format_number(value, 1) if value != 0.0 else "0.0"
|
date_time.text = Global.format_number(value, 1) if value != 0.0 else "0.0"
|
||||||
if date_time.text != text:
|
|
||||||
date_time.text = text
|
|
||||||
|
|
||||||
#region Item manipulations: update and visibility
|
#region Item manipulations: update and visibility
|
||||||
|
|
||||||
@@ -103,27 +97,8 @@ func set_datetime(value: float):
|
|||||||
func set_owniverse_item_value() -> void:
|
func set_owniverse_item_value() -> void:
|
||||||
for item_id in owniverse.items_to_update:
|
for item_id in owniverse.items_to_update:
|
||||||
if owniverse_items.has(item_id):
|
if owniverse_items.has(item_id):
|
||||||
var item: OwniverseItem = owniverse_items[item_id]
|
owniverse_items[item_id].set_label(owniverse.items_to_update[item_id])
|
||||||
if item.is_visible_in_tree() and item.get_global_rect().intersects(stage_page_scroller.get_global_rect()):
|
owniverse.items_to_update.clear()
|
||||||
item.set_label(owniverse.items_to_update[item_id])
|
|
||||||
|
|
||||||
|
|
||||||
func _refresh_ui(force: bool = false) -> void:
|
|
||||||
if force or owniverse.item_amounts_dirty:
|
|
||||||
owniverse.refresh_item_amounts(true)
|
|
||||||
set_owniverse_item_value()
|
|
||||||
set_datetime(owniverse.state.time.year)
|
|
||||||
_set_active_page_actions()
|
|
||||||
ui_elapsed = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
func _update_ui(delta: float) -> void:
|
|
||||||
ui_elapsed += delta
|
|
||||||
var position_in_pages: float = float(stage_page_scroller.scroll_horizontal) / PAGE_WIDTH
|
|
||||||
var pages := Vector2i(floori(position_in_pages), ceili(position_in_pages))
|
|
||||||
if pages != ui_pages or ui_elapsed >= UI_UPDATE_INTERVAL:
|
|
||||||
ui_pages = pages
|
|
||||||
_refresh_ui()
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -244,9 +219,6 @@ func item_activated(item: OwniverseItem) -> void:
|
|||||||
|
|
||||||
if item.item_id == "S" or item.item_id == "H":
|
if item.item_id == "S" or item.item_id == "H":
|
||||||
var _value = owniverse.produce_hs(item.item_id, 100_000)
|
var _value = owniverse.produce_hs(item.item_id, 100_000)
|
||||||
_unlock_items()
|
|
||||||
_refresh_ui(true)
|
|
||||||
_refresh_descriptions()
|
|
||||||
|
|
||||||
|
|
||||||
func _check_counters(delta: float) -> Array:
|
func _check_counters(delta: float) -> Array:
|
||||||
@@ -305,8 +277,6 @@ func _input(event: InputEvent) -> void:
|
|||||||
func _set_active_page_actions() -> void:
|
func _set_active_page_actions() -> void:
|
||||||
for pa in page_actions:
|
for pa in page_actions:
|
||||||
if !g_active_items.has(pa): continue
|
if !g_active_items.has(pa): continue
|
||||||
if not page_actions[pa].is_visible_in_tree() or not page_actions[pa].get_global_rect().intersects(stage_page_scroller.get_global_rect()):
|
|
||||||
continue
|
|
||||||
|
|
||||||
var item_id: String = g_active_items[pa].item_id
|
var item_id: String = g_active_items[pa].item_id
|
||||||
var entity: OwniverseEntity = owniverse.entities[item_id]
|
var entity: OwniverseEntity = owniverse.entities[item_id]
|
||||||
@@ -332,17 +302,12 @@ func _page_action_pressed(page_id: int, action_amount: float) -> void:
|
|||||||
|
|
||||||
if action_amount > 0:
|
if action_amount > 0:
|
||||||
owniverse.produce(item_id, action_amount)
|
owniverse.produce(item_id, action_amount)
|
||||||
_unlock_items()
|
|
||||||
_refresh_ui(true)
|
|
||||||
_refresh_descriptions()
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
get_tree().auto_accept_quit = false
|
|
||||||
owniverse.defer_item_updates = true
|
|
||||||
var inited_result = init_enitities()
|
var inited_result = init_enitities()
|
||||||
locked_items = inited_result.locked_items
|
locked_items = inited_result.locked_items
|
||||||
load_data()
|
load_data()
|
||||||
@@ -352,7 +317,6 @@ func _ready() -> void:
|
|||||||
set_datetime(owniverse.state.time.year)
|
set_datetime(owniverse.state.time.year)
|
||||||
set_owniverse_item_value()
|
set_owniverse_item_value()
|
||||||
_unlock_items()
|
_unlock_items()
|
||||||
_refresh_ui(true)
|
|
||||||
|
|
||||||
g_counters.swipe.sec = 0.2
|
g_counters.swipe.sec = 0.2
|
||||||
|
|
||||||
@@ -365,7 +329,8 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
owniverse.evolute(delta)
|
owniverse.evolute(delta)
|
||||||
_update_descriptions(delta)
|
_update_descriptions(delta)
|
||||||
_update_ui(delta)
|
set_owniverse_item_value()
|
||||||
|
set_datetime(owniverse.state.time.year)
|
||||||
|
|
||||||
#owniverse.cycle(delta)
|
#owniverse.cycle(delta)
|
||||||
|
|
||||||
@@ -374,6 +339,7 @@ func _process(delta: float) -> void:
|
|||||||
var actions = _check_counters(delta)
|
var actions = _check_counters(delta)
|
||||||
_do_actions(actions)
|
_do_actions(actions)
|
||||||
|
|
||||||
|
_set_active_page_actions()
|
||||||
|
|
||||||
|
|
||||||
func _change_speed(delta: int) -> void:
|
func _change_speed(delta: int) -> void:
|
||||||
@@ -440,8 +406,10 @@ func load_data() -> bool:
|
|||||||
SaveManager.blocked_saves["owniverse"] = true
|
SaveManager.blocked_saves["owniverse"] = true
|
||||||
push_error("Save is missing the current entity: " + id)
|
push_error("Save is missing the current entity: " + id)
|
||||||
return false
|
return false
|
||||||
owniverse.state = restored
|
var complete: Dictionary = owniverse.state.duplicate(true)
|
||||||
owniverse.new_state = restored.duplicate(true)
|
complete.merge(restored, true)
|
||||||
|
owniverse.state = complete
|
||||||
|
owniverse.new_state = complete
|
||||||
owniverse.initialize_life_batches()
|
owniverse.initialize_life_batches()
|
||||||
owniverse.refresh_item_amounts()
|
owniverse.refresh_item_amounts()
|
||||||
pending_page = clampi(int(game_data.get("ui", {}).get("current_page", 0)), 0, PAGE_COUNT - 1)
|
pending_page = clampi(int(game_data.get("ui", {}).get("current_page", 0)), 0, PAGE_COUNT - 1)
|
||||||
@@ -455,41 +423,21 @@ func _restore_page() -> void:
|
|||||||
if pending_page >= 0:
|
if pending_page >= 0:
|
||||||
stage_page_scroller.scroll_horizontal = pending_page * PAGE_WIDTH
|
stage_page_scroller.scroll_horizontal = pending_page * PAGE_WIDTH
|
||||||
pending_page = -1
|
pending_page = -1
|
||||||
_refresh_ui(true)
|
|
||||||
|
|
||||||
|
|
||||||
func save_data() -> void:
|
func save_data() -> void:
|
||||||
var page: int = pending_page
|
var page: int = pending_page
|
||||||
if page < 0:
|
if page < 0:
|
||||||
page = clampi(roundi(float(stage_page_scroller.scroll_horizontal) / PAGE_WIDTH), 0, PAGE_COUNT - 1)
|
page = clampi(roundi(float(stage_page_scroller.scroll_horizontal) / PAGE_WIDTH), 0, PAGE_COUNT - 1)
|
||||||
SaveManager.request_save("owniverse", "qwerty", {
|
SaveManager.save_data("owniverse", "qwerty", {
|
||||||
"state": owniverse.state,
|
"state": owniverse.state,
|
||||||
"ui": {"current_page": page},
|
"ui": {"current_page": page},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
func _notification(what: int) -> void:
|
func _notification(what: int) -> void:
|
||||||
if not is_node_ready() or closing:
|
if is_node_ready() and (what == NOTIFICATION_WM_CLOSE_REQUEST or what == NOTIFICATION_APPLICATION_PAUSED):
|
||||||
return
|
|
||||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
|
||||||
_close_after_save()
|
|
||||||
elif what == NOTIFICATION_APPLICATION_PAUSED:
|
|
||||||
save_data()
|
save_data()
|
||||||
# Mobile platforms can suspend all processing immediately after this event.
|
|
||||||
SaveManager.flush_saves()
|
|
||||||
|
|
||||||
|
|
||||||
func _close_after_save() -> void:
|
|
||||||
closing = true
|
|
||||||
set_process(false)
|
|
||||||
set_process_input(false)
|
|
||||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
# Stop GUI actions too while the final snapshot is being written.
|
|
||||||
process_mode = Node.PROCESS_MODE_DISABLED
|
|
||||||
save_data()
|
|
||||||
if SaveManager.is_saving():
|
|
||||||
await SaveManager.saves_drained
|
|
||||||
get_tree().quit()
|
|
||||||
|
|
||||||
|
|
||||||
func init_enitities() -> Dictionary:
|
func init_enitities() -> Dictionary:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Проверки поэтапного переноса из Owniverse.U
|
# Проверки поэтапного переноса из Owniverse.U
|
||||||
|
|
||||||
|
`test_stellar_rules.gd` также проверяет автоматическое создание HBD: долю 0,1% нового автоматического водорода, появление по одному со случайным округлением, сохранение оплаченного остатка топлива, отсутствие повторного начисления на старый запас, продолжение случайной последовательности после сериализации и переход HBD → CBD с сохранением счётчика BD. Формулы описаны в `../simulation_rules.md`.
|
||||||
|
|
||||||
## Фоновое сохранение и частота обновления интерфейса
|
## Фоновое сохранение и частота обновления интерфейса
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -15,15 +15,33 @@ func check(value: bool, message: String) -> void:
|
|||||||
func run() -> void:
|
func run() -> void:
|
||||||
var actions = load("res://ui/actions/actions.tscn").instantiate()
|
var actions = load("res://ui/actions/actions.tscn").instantiate()
|
||||||
root.add_child(actions)
|
root.add_child(actions)
|
||||||
var values: Array = [0.1, 0.01, 0.001, 1.0, 10.0]
|
for maximum in [0.0, 0.1, 0.99, 1.0, 1.9, 10.0, 100.0, 123.0, 1000.0, 10000.0, 1e9, 1.1e9, 1e150]:
|
||||||
var labels: Array = []
|
actions.set_actions("test", maximum)
|
||||||
for value in values:
|
var labels: Array = []
|
||||||
labels.append(actions._format_action_value(value))
|
var values: Array = []
|
||||||
check(labels == ["0.10", "0.010", "0.0010", "1", "10"], "Fractional action amounts retain precision")
|
for i in 5:
|
||||||
check(labels[0] != labels[1] and labels[1] != labels[2], "Adjacent fractional amounts are distinguishable")
|
var value: float = actions.action_values[i]
|
||||||
actions.set_actions("test", 123.0)
|
var text: String = actions.action_labels[i].text
|
||||||
check(actions.action_values[0] == 1.0 and actions.action_values[1] == 10.0, "Distribution preserves distinct requests")
|
check(value == floorf(value) and value <= floorf(maximum), "Whole affordable request")
|
||||||
check(actions.action_labels[0].text != actions.action_labels[1].text, "Rendered adjacent buttons are distinct")
|
if value > 0.0:
|
||||||
|
check(not values.has(value) and not labels.has(text), "Unique requests and displayed labels")
|
||||||
|
values.append(value)
|
||||||
|
labels.append(text)
|
||||||
|
else:
|
||||||
|
check(text == "" and actions.action_labels[i].get_parent().get_node("TextureButton").disabled, "Unused button blank and disabled")
|
||||||
|
if maximum >= 1.0: check(values.back() == floorf(maximum), "Maximum retained")
|
||||||
|
actions.set_actions("test", 100.0)
|
||||||
|
check(actions.action_values.values() == [1.0, 10.0, 100.0, 0.0, 0.0], "Exact power of ten not duplicated")
|
||||||
|
actions.set_actions("SMBH", 25.0, func(_request): return 25.0)
|
||||||
|
check(actions.action_values.values() == [25.0, 0.0, 0.0, 0.0, 0.0], "Identical SMBH previews collapse to one button")
|
||||||
|
actions.set_actions("step", 123.0, func(request): return floorf(request / 5.0) * 5.0)
|
||||||
|
check(actions.action_values.values() == [10.0, 100.0, 123.0, 0.0, 0.0], "Unproducible small requests omitted")
|
||||||
|
check(actions.action_labels[2].text == "120", "Recipe preview displayed while original request retained")
|
||||||
|
var emitted: Array = []
|
||||||
|
actions.action_pressed.connect(func(_page, amount): emitted.append(amount))
|
||||||
|
actions._on_action_button_pressed(2)
|
||||||
|
actions._on_action_button_pressed(4)
|
||||||
|
check(emitted == [123.0], "Only active button emits its matching request")
|
||||||
actions.queue_free()
|
actions.queue_free()
|
||||||
print("Action values: %d checks, %d failures" % [checks, failures])
|
print("Action values: %d checks, %d failures" % [checks, failures])
|
||||||
quit(0 if failures == 0 else 1)
|
quit(0 if failures == 0 else 1)
|
||||||
|
|||||||
@@ -168,6 +168,65 @@ func run() -> void:
|
|||||||
check(world.state.binds.RD.HP >= 1.0, "Automatic formation also creates planets")
|
check(world.state.binds.RD.HP >= 1.0, "Automatic formation also creates planets")
|
||||||
dispose(world)
|
dispose(world)
|
||||||
|
|
||||||
|
world = fixture()
|
||||||
|
world.entities.N.auto = 1.0
|
||||||
|
# 1000 newly produced automatic H reserve 1 H, enough for 10 HBD.
|
||||||
|
world.evolute(100.0)
|
||||||
|
check(world.state.amount.HBD == 10.0, "Brown dwarfs form without existing ordinary stars")
|
||||||
|
check(world.state.amount.H == 999.0 and world.state.auto_resources.H == 999.0, "Brown dwarfs consume exactly their reserved hydrogen")
|
||||||
|
check(world.get_item_amounts().BD == 10.0 and world.state.batches.HBD.size() > 0, "Brown dwarfs appear in counter and evolution batches")
|
||||||
|
world.entities.N.product_per_year_amount = 0.0
|
||||||
|
world.evolute(100.0)
|
||||||
|
check(world.state.amount.HBD == 10.0, "Stored automatic budget is not taxed repeatedly")
|
||||||
|
dispose(world)
|
||||||
|
|
||||||
|
world = fixture()
|
||||||
|
world.entities.N.auto = 1.0
|
||||||
|
world.state.amount.RD = 1.0
|
||||||
|
world.evolute(50.0)
|
||||||
|
check(world.state.amount.HBD == 5.0 and is_equal_approx(world.state.auto_star_deficits.HBD_fuel, 0.5), "Individual dwarfs retain prepaid fractional fuel")
|
||||||
|
check(world.state.amount.H == 0.0, "Prepaid fuel is not available to ordinary stars")
|
||||||
|
var resumed = fixture()
|
||||||
|
resumed.entities.N.auto = 1.0
|
||||||
|
resumed.state = bytes_to_var(var_to_bytes(world.state))
|
||||||
|
world.evolute(50.0)
|
||||||
|
resumed.evolute(50.0)
|
||||||
|
check(world.state.amount.HBD == 10.0, "Accumulated quota eventually creates a whole batch")
|
||||||
|
check(world.state == resumed.state, "Brown dwarf quota survives serialization deterministically")
|
||||||
|
check(world.state.amount.RD == 1000.0 and world.state.amount.H == 0.0, "Shared automatic hydrogen is spent once")
|
||||||
|
world.state.batches.HBD = [{"amount": 10.0, "start_year": 101.0, "end_year": 101.0}]
|
||||||
|
world._evolve_batches(world.state, 101.0)
|
||||||
|
check(world.state.amount.HBD == 0.0 and world.state.amount.CBD == 10.0 and world.get_item_amounts().BD == 10.0, "Cooling preserves aggregate brown dwarf count")
|
||||||
|
dispose(resumed)
|
||||||
|
dispose(world)
|
||||||
|
|
||||||
|
world = fixture()
|
||||||
|
var saw_individual: bool = false
|
||||||
|
for i in range(250):
|
||||||
|
world.state.amount.H += 1.0
|
||||||
|
world.state.auto_resources.H = world.state.auto_resources.get("H", 0.0) + 1.0
|
||||||
|
var before: float = world.state.amount.HBD
|
||||||
|
world._produce_brown_dwarfs(world.state, 1.0, float(i), float(i + 1))
|
||||||
|
var born: float = world.state.amount.HBD - before
|
||||||
|
check(born == 0.0 or born == 1.0, "Small fuel increments produce individual dwarfs")
|
||||||
|
saw_individual = saw_individual or born == 1.0
|
||||||
|
check(saw_individual, "Random rounding does not wait for ten dwarfs")
|
||||||
|
resumed = fixture()
|
||||||
|
resumed.state = bytes_to_var(var_to_bytes(world.state))
|
||||||
|
for i in range(250, 500):
|
||||||
|
for simulation in [world, resumed]:
|
||||||
|
simulation.state.amount.H += 1.0
|
||||||
|
simulation.state.auto_resources.H += 1.0
|
||||||
|
simulation._produce_brown_dwarfs(simulation.state, 1.0, float(i), float(i + 1))
|
||||||
|
check(world.state == resumed.state, "Random sequence and prepaid fuel survive saving")
|
||||||
|
check(is_equal_approx(world.state.amount.H + world.state.amount.HBD * 0.1 + world.state.auto_star_deficits.HBD_fuel, 500.0), "Individual production conserves fuel including prepaid reserve")
|
||||||
|
check(is_equal_approx(world.state.amount.HBD + world.state.auto_star_deficits.HBD, 5.0), "Signed remainder preserves the 0.1 percent allocation")
|
||||||
|
var paused_count: float = world.state.amount.HBD
|
||||||
|
for i in 10: world._produce_brown_dwarfs(world.state, 0.0, 500.0, 501.0)
|
||||||
|
check(world.state.amount.HBD == paused_count, "No repeated random attempts without new fuel")
|
||||||
|
dispose(resumed)
|
||||||
|
dispose(world)
|
||||||
|
|
||||||
# Two simultaneous successors share surviving planets in the same 99:1 ratio.
|
# Two simultaneous successors share surviving planets in the same 99:1 ratio.
|
||||||
world = fixture()
|
world = fixture()
|
||||||
world.entities.HRG.destroy_level = 0
|
world.entities.HRG.destroy_level = 0
|
||||||
|
|||||||
+33
-21
@@ -26,21 +26,37 @@ var action_values: Dictionary = {
|
|||||||
func set_actions(item_id: String, value: float, preview: Callable = Callable()) -> void:
|
func set_actions(item_id: String, value: float, preview: Callable = Callable()) -> void:
|
||||||
current_item_id = item_id
|
current_item_id = item_id
|
||||||
var requests: Array = _get_distribution(value)
|
var requests: Array = _get_distribution(value)
|
||||||
var displayed: Array = requests.duplicate()
|
var unique_requests: Array = []
|
||||||
if preview.is_valid():
|
var displayed: Array = []
|
||||||
for i in range(displayed.size()):
|
var labels: Dictionary = {}
|
||||||
displayed[i] = preview.call(requests[i]) if requests[i] > 0.0 else 0.0
|
# Prefer the largest request (including MAX) when previews or rounded
|
||||||
_set_action_values(requests, displayed)
|
# scientific labels coincide. Keep the request paired with its preview.
|
||||||
|
for i in range(requests.size() - 1, -1, -1):
|
||||||
|
if requests[i] <= 0.0: continue
|
||||||
|
var amount: float = preview.call(requests[i]) if preview.is_valid() else requests[i]
|
||||||
|
if not is_finite(amount) or amount < 1.0 or amount != floorf(amount): continue
|
||||||
|
var text: String = _format_action_value(amount)
|
||||||
|
if displayed.has(amount) or labels.has(text): continue
|
||||||
|
labels[text] = true
|
||||||
|
unique_requests.push_front(requests[i])
|
||||||
|
displayed.push_front(amount)
|
||||||
|
while unique_requests.size() < 5:
|
||||||
|
unique_requests.append(0.0)
|
||||||
|
displayed.append(0.0)
|
||||||
|
_set_action_values(unique_requests, displayed)
|
||||||
|
|
||||||
func _get_distribution(value: float) -> Array:
|
func _get_distribution(value: float) -> Array:
|
||||||
if value <= 0.0 or not is_finite(value):
|
if value < 1.0 or not is_finite(value):
|
||||||
return [0.0, 0.0, 0.0, 0.0, 0.0]
|
return [0.0, 0.0, 0.0, 0.0, 0.0]
|
||||||
var distribution: Array = [value, 0, 0, 0, 0]
|
value = floorf(value)
|
||||||
|
var distribution: Array = []
|
||||||
var exp_value = int(floor(log(abs(value)) / log(10.0)))
|
var power: float = 1.0
|
||||||
var floor_value = max(exp_value - 4, -1)
|
while power < value:
|
||||||
for ev in range(exp_value, floor_value, -1):
|
distribution.append(power)
|
||||||
distribution.push_front(pow(10.0, ev))
|
if distribution.size() > 4: distribution.pop_front()
|
||||||
|
power *= 10.0
|
||||||
|
distribution.append(value)
|
||||||
|
while distribution.size() < 5: distribution.append(0.0)
|
||||||
return distribution
|
return distribution
|
||||||
|
|
||||||
func _set_action_values(values: Array, displayed: Array) -> void:
|
func _set_action_values(values: Array, displayed: Array) -> void:
|
||||||
@@ -50,19 +66,15 @@ func _set_action_values(values: Array, displayed: Array) -> void:
|
|||||||
var text: String = _format_action_value(displayed[ind])
|
var text: String = _format_action_value(displayed[ind])
|
||||||
if action_labels[ind].text != text:
|
if action_labels[ind].text != text:
|
||||||
action_labels[ind].text = text
|
action_labels[ind].text = text
|
||||||
|
action_labels[ind].get_parent().get_node("TextureButton").disabled = values[ind] <= 0.0
|
||||||
|
|
||||||
|
|
||||||
func _format_action_value(value: float) -> String:
|
func _format_action_value(value: float) -> String:
|
||||||
if value <= 0.0 or not is_finite(value):
|
if value < 1.0 or not is_finite(value) or value != floorf(value):
|
||||||
return ""
|
return ""
|
||||||
if value >= 1.0:
|
return Global.format_number(value)
|
||||||
return Global.format_number(value)
|
|
||||||
# Global's default is intended for object counts. Action values may be
|
|
||||||
# fractional, so retain enough precision to keep adjacent buttons distinct.
|
|
||||||
var exponent: int = int(floor(log(value) / log(10.0)))
|
|
||||||
var digits: int = clampi(-exponent + 1, 2, 6)
|
|
||||||
return Global.format_number(value, digits)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_action_button_pressed(button_id: int) -> void:
|
func _on_action_button_pressed(button_id: int) -> void:
|
||||||
action_pressed.emit(page_id, action_values[button_id])
|
if action_values.get(button_id, 0.0) > 0.0:
|
||||||
|
action_pressed.emit(page_id, action_values[button_id])
|
||||||
|
|||||||
@@ -997,6 +997,8 @@ func _produce_for_period(previous: Dictionary, target: Dictionary, quantities: D
|
|||||||
for id in budget:
|
for id in budget:
|
||||||
budget[id] = minf(maxf(0.0, budget[id]), _available(target, id))
|
budget[id] = minf(maxf(0.0, budget[id]), _available(target, id))
|
||||||
target.auto_resources = budget
|
target.auto_resources = budget
|
||||||
|
_produce_brown_dwarfs(target, automatic.get("H", 0.0), from_year, to_year)
|
||||||
|
budget = _ordinary_star_budget(target)
|
||||||
|
|
||||||
# One common multiplier preserves the initial count ratios, even under scarcity.
|
# One common multiplier preserves the initial count ratios, even under scarcity.
|
||||||
var candidates: Dictionary = {}
|
var candidates: Dictionary = {}
|
||||||
@@ -1059,7 +1061,7 @@ func _produce_for_period(previous: Dictionary, target: Dictionary, quantities: D
|
|||||||
continue
|
continue
|
||||||
if not _try_production(target, entity, actual, true):
|
if not _try_production(target, entity, actual, true):
|
||||||
continue
|
continue
|
||||||
budget = target.auto_resources
|
budget = _ordinary_star_budget(target)
|
||||||
created[id] = actual
|
created[id] = actual
|
||||||
total_created += actual
|
total_created += actual
|
||||||
for id in candidates:
|
for id in candidates:
|
||||||
@@ -1071,6 +1073,48 @@ func _produce_for_period(previous: Dictionary, target: Dictionary, quantities: D
|
|||||||
_create_planets(target, id, created[id], from_year, to_year, metallicity)
|
_create_planets(target, id, created[id], from_year, to_year, metallicity)
|
||||||
|
|
||||||
|
|
||||||
|
# HBD quota is in objects. HBD_fuel is prepaid hydrogen removed from free stock.
|
||||||
|
# Signed rounding debt and the PRNG state survive saving with this dictionary.
|
||||||
|
func _produce_brown_dwarfs(target: Dictionary, new_hydrogen: float, from_year: float, to_year: float) -> void:
|
||||||
|
if not entities.has("HBD"):
|
||||||
|
return
|
||||||
|
var entity: OwniverseEntity = entities.HBD
|
||||||
|
var cost: float = entity.cost.get("H", 0.0)
|
||||||
|
if cost <= 0.0:
|
||||||
|
return
|
||||||
|
var quota: float = target.auto_star_deficits.get("HBD", 0.0) + maxf(0.0, new_hydrogen) * 0.001 / cost
|
||||||
|
var fuel: float = maxf(0.0, target.auto_star_deficits.get("HBD_fuel", 0.0))
|
||||||
|
var available: float = minf(target.auto_resources.get("H", 0.0), _available(target, "H"))
|
||||||
|
quota = minf(quota, (available + fuel) / cost)
|
||||||
|
var expected: float = maxf(0.0, snappedf(quota, 1e-9))
|
||||||
|
var amount: float = floorf(expected)
|
||||||
|
# Do not retry a fractional chance on frames with no new automatic fuel.
|
||||||
|
if new_hydrogen > 0.0 and expected > amount:
|
||||||
|
var random_state: float = target.auto_star_deficits.get("HBD_random", 1234567.0)
|
||||||
|
random_state = fmod(random_state * 48271.0, 2147483647.0)
|
||||||
|
target.auto_star_deficits.HBD_random = random_state
|
||||||
|
if random_state / 2147483647.0 < expected - amount:
|
||||||
|
amount += 1.0
|
||||||
|
amount = minf(amount, floorf(snappedf((available + fuel) / cost, 1e-9)))
|
||||||
|
var charge: float = ceilf(maxf(0.0, snappedf(amount * cost - fuel, 1e-9)))
|
||||||
|
if amount > 0.0 and charge <= available:
|
||||||
|
_take_available(target, "H", charge)
|
||||||
|
target.auto_resources.H = maxf(0.0, target.auto_resources.get("H", 0.0) - charge)
|
||||||
|
target.auto_star_deficits.HBD_fuel = maxf(0.0, snappedf(fuel + charge - amount * cost, 1e-9))
|
||||||
|
quota -= amount
|
||||||
|
_credit(target, "HBD", amount)
|
||||||
|
_append_batch(target, "HBD", amount, from_year, to_year)
|
||||||
|
target.auto_star_deficits.HBD = quota
|
||||||
|
|
||||||
|
|
||||||
|
func _ordinary_star_budget(target: Dictionary) -> Dictionary:
|
||||||
|
var budget: Dictionary = target.auto_resources.duplicate()
|
||||||
|
if entities.has("HBD"):
|
||||||
|
var reserved: float = maxf(0.0, target.auto_star_deficits.get("HBD", 0.0) * entities.HBD.cost.get("H", 0.0) - target.auto_star_deficits.get("HBD_fuel", 0.0))
|
||||||
|
budget.H = maxf(0.0, budget.get("H", 0.0) - reserved)
|
||||||
|
return budget
|
||||||
|
|
||||||
|
|
||||||
func _auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Dictionary) -> float:
|
func _auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Dictionary) -> float:
|
||||||
var capacity: float = INF
|
var capacity: float = INF
|
||||||
var needs: Dictionary = {}
|
var needs: Dictionary = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user