extends Node const PROFILES := preload("res://scripts/planets/planet_profiles.gd") const RULES := preload("res://scripts/planets/poi_rules.gd") const CAPTAIN := preload("res://scripts/captain/captain_progress.gd") var failures: Array[String] = [] func check(value: bool, message: String) -> void: if not value: failures.append(message) push_error(message) func equip(captain: Resource, rank: int) -> void: for item in captain.gear_items: item.level = rank func _ready() -> void: var captain := CAPTAIN.new() var state: Resource = captain.expedition var statistics: Array[Dictionary] = [] for level in [1,10,30,60,120]: equip(captain,level) captain.level = level var ore := 0.0 var damage := 0.0 var recovery := 0.0 var difficulty := 0.0 for seed_value in 200: var profile := PROFILES.generate(seed_value+711,0,"Test",level) var poi := {"id":seed_value,"kind":"deposit","complete":false,"bonus_available":false} var plan := RULES.preview(profile,captain,poi,1234) for amount in plan.materials.values(): ore += float(amount) damage += (plan.damage.x+plan.damage.y)*0.5 recovery += float(RULES.report("deposit",profile,captain).multiplier) difficulty += float(profile.difficulty) if level == 1: check(profile.easy and profile.landing_damage == 5,"first-level planets are gentle") check(is_equal_approx(RULES.report("deposit",profile,captain).multiplier,1.0),"starter equipment recovers 100 percent") check(plan.damage == Vector2i(2,4),"first-level ordinary damage stays 2–4 HP") statistics.append({"level":level,"mean_materials":ore/200,"mean_damage":damage/200,"mean_recovery":recovery/200,"mean_difficulty":difficulty/200}) for index in range(1,statistics.size()): check(statistics[index].mean_materials > statistics[index-1].mean_materials,"rewards grow with planet level") check(statistics[index].mean_damage > statistics[index-1].mean_damage,"matched-equipment wear grows gradually") check(statistics[index].mean_difficulty > statistics[index-1].mean_difficulty,"planet difficulty grows gradually") equip(captain,1) captain.level = 1 var profile := PROFILES.generate(123,0,"Pierwsza planeta",1) state.lander_hp = 100 check(state.begin_visit(profile),"first expedition begins") var permanent := captain.minerals.duplicate() for index in 10: var poi := {"id":index,"kind":"deposit","complete":false,"bonus_available":false} var hp: float = state.lander_hp var plan := RULES.preview(profile,captain,poi,state.visit_seed) check(plan == RULES.preview(profile,captain,poi,state.visit_seed),"reopening forecast never rerolls") check(state.lander_hp == hp and not poi.complete,"forecast is read-only") var result: Dictionary = state.resolve_poi(captain,poi) check(result.materials == plan.materials,"actual resources match forecast exactly") check(result.damage >= plan.damage.x and result.damage <= plan.damage.y,"damage stays within forecast") check(is_equal_approx(state.lander_hp,hp-result.damage),"ordinary POI damages lander") var cargo: Dictionary = state.cargo.duplicate() check(state.resolve_poi(captain,poi).is_empty() and state.cargo == cargo,"ordinary rewards cannot repeat") check(state.lander_hp >= 55 and state.lander_hp <= 75,"ten starter POIs leave 55–75 HP") check(captain.minerals == permanent,"loot remains pending until return") state.finish_visit(captain,true) check(captain.minerals != permanent,"safe return delivers cargo") var advanced := PROFILES.generate(123,0,"Trudna planeta",120) equip(captain,50) check(RULES.report("cave",advanced,captain).multiplier < 0.9,"level 50 gear does not neutralize endgame planets") check(RULES.damage_range("cave",advanced,captain).x > 8,"outdated gear increases damage") equip(captain,20) var middle := PROFILES.generate(123,0,"Test sprzętu",20) var cave_before := RULES.damage_range("cave",middle,captain).x var warehouse_before := RULES.damage_range("warehouse",middle,captain).x captain.gear_items[0].level = 1 check(RULES.damage_range("cave",middle,captain).x-cave_before > RULES.damage_range("warehouse",middle,captain).x-warehouse_before,"breathing gear matters most in caves") equip(captain,1) state.lander_hp = 100 check(state.begin_visit(profile),"bonus expedition begins") var bonus_poi := {"id":100,"kind":"deposit","complete":false,"bonus_available":true} check(state.resolve_poi(captain,bonus_poi,null,true).is_empty(),"bonus requires initial exploration") state.resolve_poi(captain,bonus_poi) var bonus_plan := RULES.preview(profile,captain,bonus_poi,state.visit_seed,true) check(bonus_plan.damage == Vector2i(8,18),"starter bonus damage is 8–18 HP") var cargo_before: Dictionary = state.cargo.duplicate() state.resolve_poi(captain,bonus_poi,null,true) check(bonus_poi.bonus_complete and state.cargo != cargo_before,"optional bonus awards extra loot once") check(state.resolve_poi(captain,bonus_poi,null,true).is_empty(),"bonus cannot repeat") var lethal := {"id":101,"kind":"village","complete":false,"bonus_available":true} state.resolve_poi(captain,lethal) state.lander_hp = 1 var credits: int = captain.credits var minerals: Dictionary = captain.minerals.duplicate() state.resolve_poi(captain,lethal,null,true) check(state.lander_hp == 0,"greedy exploration can destroy the lander") var returned: Dictionary = state.finish_visit(captain,true) check(not returned.success and captain.credits == credits and captain.minerals == minerals,"destruction loses expedition loot without touching owned resources") var bonus_count := 0 for index in 2000: if RULES.has_bonus({"id":index,"kind":"deposit"},profile): bonus_count += 1 check(bonus_count >= 400 and bonus_count <= 600,"roughly one quarter of POIs offer bonus exploration") var file := FileAccess.open("res://artifacts/planet_exploration_balance.json",FileAccess.WRITE) file.store_string(JSON.stringify({"statistics":statistics,"bonus_fraction":bonus_count/2000.0,"failures":failures},"\t")) file.close() print("PLANET_EXPLORATION_BALANCE: ","PASS" if failures.is_empty() else "FAIL"," ",JSON.stringify(statistics)) get_tree().quit(0 if failures.is_empty() else 1)