extends Node2D const GEN := preload("res://scripts/items/item_generator.gd") const DB := preload("res://resources/items/affix_database.tres") const CATALOG := preload("res://scripts/game/ship_catalog.gd") var player: CharacterBody2D var inventory: ShipInventory var report := {} func _ready() -> void: GameState.start_new_run() GameState.system_level = 120 seed(1202026) player = preload("res://scenes/game/player_ship.tscn").instantiate() add_child(player) player.set_physics_process(false) inventory = player.get_node("ShipInventory") player.get_node("WeaponController").set_physics_process(false) inventory.unlocked_weapon_slots = 3 for tier in [0,2]: var samples: Array[Dictionary] = [] for sample in 500: loadout(tier) samples.append(metrics()) var summary := {} for key in samples[0]: var values: Array = samples.map(func(row): return row[key]) values.sort() summary[key] = {"min":values[0],"median":values[250],"p90":values[450],"max":values[499]} summary["single_gun_sustainable_fraction"] = float(samples.filter(func(row): return row.regen >= row.single_gun_cost_s).size())/samples.size() summary["two_guns_sustainable_fraction"] = float(samples.filter(func(row): return row.regen >= row.two_guns_cost_s).size())/samples.size() report["random_boss" if tier==2 else "random_normal"] = summary for focus in ["speed","energy","shield"]: loadout(2,focus) report[focus+"_max"] = metrics() if focus == "energy": report["energy_runtime"] = energy_simulation(30.0) if focus == "speed": report["movement_runtime"] = movement_simulation() if focus == "shield": report["shield_runtime"] = shield_simulation() player.shields = 0.0 player._refill_recovery_budgets(1.0) player.restore_shields_from_damage(1000.0) report["shield_leech_actual_from_1000_damage"] = player.shields loadout(0) report["representative"] = metrics() report["representative_energy_runtime"] = energy_simulation(30.0) report["representative_single_gun_runtime"] = energy_simulation(30.0,1) var star := Node2D.new() add_child(star) star.position = player.position player.charging_star = star report["solar_regen"] = player.get_energy_regeneration() report["solar_two_guns_runtime"] = energy_simulation(30.0) player.charging_star = null var shields: ItemInstance = inventory.module_slots[0] shields.special_orbs.append("polarization") report["polarization"] = {"per_hit":player.max_shields*0.05,"energy_per_s_at_5_hits":player.max_shields*0.05*5,"generator_capacity":player.max_energy} player.energy = 0.0 player.shields = player.max_shields var before: float = player.shields for hit in 5: player.take_damage(1.0) report["polarization_actual_5_tiny_hits"] = {"shield_damage":before-player.shields,"energy_gained":player.energy} var engine: ItemInstance = inventory.module_slots[2] engine.special_orbs.append("atomic_engine") report["atomic_engine_cost_s"] = player.get_mini_boost_energy_cost() engine.special_orbs.append("booster") report["booster_cost_s"] = player.get_mini_boost_energy_cost() var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate() add_child(enemy) enemy.set_physics_process(false) var enemy_metrics := [] for tier in [0,1,2]: enemy.apply_level_settings(120,tier) enemy_metrics.append({"tier":tier,"hull":enemy.max_hull,"shields":enemy.max_shields,"dps":enemy.get_weapon_dps(),"speed":enemy.approach_speed}) report["enemies"] = enemy_metrics var laser_results: Array[Dictionary] = [] for sample in 200: loadout(0) for slot in 2: inventory.weapon_slots[slot] = GEN.generate_for_definition(preload("res://resources/items/beam_laser.tres"),120) inventory._recalculate_ship_stats() laser_results.append(metrics()) var laser_costs: Array = laser_results.map(func(row): return row.two_guns_cost_s) laser_costs.sort() report["lasers"] = {"samples":200,"median_two_lasers_cost_s":laser_costs[100],"two_lasers_sustainable_fraction":float(laser_results.filter(func(row): return row.regen >= row.two_guns_cost_s).size())/200.0} var file := FileAccess.open("res://artifacts/ship_level_120_audit.json",FileAccess.WRITE) file.store_string(JSON.stringify(report,"\t")) file.close() print("SHIP_120_AUDIT_DONE: "+JSON.stringify(report)) func loadout(tier: int, focus := "") -> void: player.base_max_speed = 108.0 player.base_max_hull = 100.0 player.base_max_shields = 100.0 player.base_max_energy = 100.0 var definitions := ["plasma_cannon","plasma_cannon","missile_launcher","reinforced_shield","hull_plating","engine_extension","compact_generator","ship_computer"] for index in definitions.size(): var definition := load("res://resources/items/%s.tres" % definitions[index]) as ItemDefinition var item := GEN.generate_for_definition(definition,120,tier) if not focus.is_empty(): var factor := 9.33 item.primary_value_override = (5.0 if definition.primary_stat=="max_speed" else definition.primary_value*1.2)*factor item.energy_regeneration_override = definition.energy_regeneration_bonus*1.2*factor if definition.category == ItemDefinition.Category.WEAPON: item.damage_min = roundf(definition.damage_lower_roll_max*factor) item.damage_max = roundf((definition.damage_lower_roll_max+definition.damage_spread_roll_max)*factor) var keys: Array = {"speed":["speed_percent","damage_percent","critical_chance_percent","critical_damage_percent","attack_speed_percent"],"energy":["energy_regeneration","attack_speed_percent","damage_percent","shield_percent","shield_regeneration"],"shield":["shield_percent","shield_regeneration","shield_on_damage_percent","damage_percent","critical_chance_percent"]}[focus] item.additional_modifiers.clear() for key in keys: for affix in DB.affixes: if affix.stat_key==key and affix.can_roll(definition,120): var addon := ItemStatModifier.new() addon.affix = affix addon.stat_key = key addon.value = affix.get_value_range(120).y item.additional_modifiers.append(addon) if index<3: inventory.weapon_slots[index]=item else: inventory.module_slots[index-3]=item inventory._recalculate_ship_stats() player.energy = player.max_energy player.shields = player.max_shields player.hull = player.max_hull player.is_docked = false func metrics() -> Dictionary: var first: ItemInstance = inventory.weapon_slots[0] var second: ItemInstance = inventory.weapon_slots[1] var cost := first.get_energy_cost()/first.get_fire_interval() var two := cost+second.get_energy_cost()/second.get_fire_interval() return {"speed":player.max_speed,"tab_speed":player.max_speed*2,"afterburner_speed":minf(600.0,player.max_speed*3),"max_energy":player.max_energy,"regen":player.get_energy_regeneration(),"single_gun_cost_s":cost,"two_guns_cost_s":two,"two_guns_and_tab_cost_s":two+player.get_mini_boost_energy_cost(),"shields":player.max_shields,"shield_regen":player.shield_regeneration,"shield_refill_s":player.max_shields/maxf(0.001,player.shield_regeneration),"shield_leech_percent":player.shield_on_damage_percent,"raw_dps":inventory.get_total_dps(),"shield_leech_s_at_50pct_accuracy_20pct_absorption":minf(player.max_shields*0.05,inventory.get_total_dps()*0.5*0.8*player.shield_on_damage_percent/100.0),"scanner_range":player.scanner_range,"stop_from_tab_s":player.max_speed*2/player.space_drag} func energy_simulation(seconds: float, guns := 2) -> Dictionary: var timers := [0.0,0.0] var fired := 0 var failed := 0 var start: float = player.max_energy player.energy = start var controller: Node = player.get_node("WeaponController") for frame in int(seconds*60): player.energy = minf(player.max_energy,player.energy+player.get_energy_regeneration()/60.0) for index in guns: timers[index] -= 1.0/60.0 if timers[index]<=0: var item: ItemInstance = inventory.weapon_slots[index] if controller._consume_shot_resource(item): fired+=1 else: failed+=1 timers[index] += item.get_fire_interval() return {"duration":seconds,"energy_start":start,"energy_end":player.energy,"shots":fired,"failed_shots":failed} func movement_simulation() -> Dictionary: player.velocity = Vector2.ZERO for frame in 1200: preload("res://scripts/game/flight_movement.gd").apply(player,1.0/60.0,0,true,false,0,true,false,player.max_speed*2) var peak: float = player.velocity.length() var seconds := 0.0 while player.velocity.length()>1 and seconds<120: preload("res://scripts/game/flight_movement.gd").apply(player,1.0/60.0,0,false,false,0,false,false,player.max_speed*2) seconds+=1.0/60.0 return {"tab_speed_after_20s":peak,"coast_to_stop_seconds":seconds,"thrust_acceleration":player.thrust_acceleration,"space_drag":player.space_drag} func shield_simulation() -> Dictionary: player.shields = 0.0 var seconds := 0.0 while player.shields