extends Node const EVENTS := preload("res://scripts/planets/surface_events.gd") const GENERATOR := preload("res://scripts/planets/surface_generator.gd") const SCENERY := preload("res://scripts/planets/surface_scenery.gd") const PROFILES := preload("res://scripts/planets/planet_profiles.gd") var captain: Resource var state: Resource var profile: Dictionary class InventoryStub extends Node: signal ammunition_changed signal inventory_changed var missile_ammunition := 2 func add_item(_item: Resource) -> bool: return false func fresh(kind: String, seed_value := 123) -> Dictionary: captain = preload("res://scripts/captain/captain_progress.gd").new() captain.credits = 100000 captain.parts = 100 for key in ["iron","copper","nickel","silicon","tungsten","mercury","sulfur","uranium"]: captain.minerals[key] = 100 state = captain.expedition assert(state.begin_visit(profile)) var event := {"kind":kind,"seed":seed_value,"complete":false} EVENTS.prepare(event,captain,state) return event func _ready() -> void: GameState.system_level = 3 profile = PROFILES.generate(777,1,"Test wydarzeń") profile.difficulty = 0.0 profile.oxygen = 95 profile.temperature = 20 profile.flora = 0 for kind in EVENTS.KINDS: var event := fresh(kind) assert(not str(EVENTS.STORIES[kind]).is_empty()) var offers := EVENTS.options(event,captain,state) assert(not offers.is_empty()) var snapshot := var_to_str(offers) assert(snapshot == var_to_str(EVENTS.options(event,captain,state))) event.rolls = [0.0,0.0,0.0] var result := EVENTS.resolve(event,str(offers[0].id),captain,state) assert(result.success) if kind == "scan_cache": assert(event.scanned and not event.complete) var before: int = captain.credits assert(not EVENTS.resolve(event,"scan",captain,state).success) assert(captain.credits == before) assert(EVENTS.resolve(event,"open",captain,state).success) if kind == "evacuation": assert(event.stage == 1 and not event.complete) assert(EVENTS.resolve(event,"help",captain,state).success) assert(EVENTS.resolve(event,"help",captain,state).success) assert(event.complete) var credits_before: int = captain.credits var cargo_before := var_to_str([state.cargo,state.cargo_credits,state.cargo_parts,state.cargo_missiles,state.cargo_modules]) assert(not EVENTS.resolve(event,str(offers[0].id),captain,state).success) assert(captain.credits == credits_before) assert(cargo_before == var_to_str([state.cargo,state.cargo_credits,state.cargo_parts,state.cargo_missiles,state.cargo_modules])) # Server-side affordability and action validation; no partial payment or reroll. var event := fresh("blind_cache") captain.credits = 0 var prize_before := var_to_str(event.prize) assert(not EVENTS.resolve(event,"open",captain,state).success) assert(not event.complete and captain.credits == 0) assert(prize_before == var_to_str(event.prize)) assert(not EVENTS.resolve(event,"forged",captain,state).success) event = fresh("rescue") captain.parts = 4 state.cargo_parts = 1 assert(not EVENTS.resolve(event,"rescue",captain,state).success) assert(captain.parts == 4 and state.cargo_parts == 1 and not event.complete) state.cargo_parts = 3 event.rolls = [1.0,1.0,1.0] var hp_before: float = state.lander_hp var result := EVENTS.resolve(event,"rescue",captain,state) assert(not result.success and event.complete and state.lander_hp < hp_before) assert(captain.parts == 1 and state.cargo_parts == 0) # A prototype pays the base even when the test fails. event = fresh("prototype") event.rolls = [1.0,1.0,1.0] assert(not EVENTS.resolve(event,"test",captain,state).success) assert(state.cargo_credits == 35*GameState.system_level and state.cargo_modules.is_empty()) # Later failed rescue stages preserve earlier rewards; stop is terminal. event = fresh("evacuation") event.rolls = [0.0,1.0,1.0] assert(EVENTS.resolve(event,"help",captain,state).success) var first_payment: int = state.cargo_credits assert(not EVENTS.resolve(event,"help",captain,state).success) assert(event.complete and state.cargo_credits == first_payment) event = fresh("evacuation") event.rolls = [0.0,0.0,0.0] EVENTS.resolve(event,"help",captain,state) first_payment = state.cargo_credits assert(EVENTS.resolve(event,"stop",captain,state).success) assert(event.complete and state.cargo_credits == first_payment) # Repair clamps correctly and is unavailable at full HP. event = fresh("service") state.lander_hp = 97 assert(EVENTS.resolve(event,"repair",captain,state).success) assert(state.lander_hp == 100) event = fresh("service") state.lander_hp = 100 assert(EVENTS.options(event,captain,state).is_empty()) assert(not EVENTS.resolve(event,"repair",captain,state).success) # Purchased rockets stay temporary, use the shop discount, and transfer once. event = fresh("trader") assert(event.missile_count >= 3 and event.missile_count <= 6) assert(event.missile_price < event.missile_normal_price) var expected: int = event.missile_count var inventory := InventoryStub.new() assert(EVENTS.resolve(event,"missiles",captain,state).success) assert(state.cargo_missiles == expected and inventory.missile_ammunition == 2) state.finish_visit(captain,true) state.deliver_modules(inventory) assert(inventory.missile_ammunition == 2+expected) state.deliver_modules(inventory) assert(inventory.missile_ammunition == 2+expected) # Already-owned goods survive destruction; all new cargo is lost. event = fresh("trader") EVENTS.resolve(event,"missiles",captain,state) state.cargo_credits = 123 state.cargo_parts = 7 state.cargo_modules.append(event.module) var committed_credits: int = captain.credits state.damage(100) assert(not state.finish_visit(captain,true).success) assert(captain.credits == committed_credits and captain.parts == 100) assert(state.delivered_missiles == 0 and state.delivered_modules.is_empty()) assert(state.cargo_credits == 0 and state.cargo_parts == 0 and state.cargo_missiles == 0) # Successful payout is idempotent and never stores parts as minerals. event = fresh("safe_bet") EVENTS.resolve(event,"safe",captain,state) state.cargo_credits = 123 committed_credits = captain.credits state.finish_visit(captain,true) state.finish_visit(captain,true) assert(captain.parts == 106 and captain.credits == committed_credits+123) assert(not captain.minerals.has("parts") and not captain.minerals.has("missile")) inventory.free() # Event modules use normal rarity rolls and a strictly decreasing biome level tail. var bonus_rng := RandomNumberGenerator.new() bonus_rng.seed = 727373 var histogram := PackedInt32Array() histogram.resize(11) for index in 100000: var bonus := EVENTS.roll_module_bonus(bonus_rng,{"difficulty":1.0}) assert(bonus >= 2 and bonus <= 10) histogram[bonus] += 1 for index in range(2,10): assert(histogram[index] > histogram[index+1]) assert(histogram[0] == 0 and histogram[1] == 0) assert(histogram[2] > 34000 and histogram[2] < 37000) assert(histogram[10] > 950 and histogram[10] < 1350) var rarities := {} for index in 1000: var item := EVENTS._module(bonus_rng,32,{"difficulty":1.0}) assert(item.item_level >= 34 and item.item_level <= 42) assert(item.rarity >= 0 and item.rarity <= 4) rarities[item.rarity] = true assert(rarities.size() == 5) assert(EVENTS._module(bonus_rng,1000,{"difficulty":1.0}).item_level == 1000) print("MODULE LOOT PASS: normal through legendary rarity, hard biome level +2..+10, decreasing exponential weights; histogram=",histogram) # Exactly one guaranteed marker, all ten kinds on usable terrain. var data := GENERATOR.generate(200,profile) data.trees = [] data.rocks = [] data.mountains = [] data.pois = [] data.heights.fill(0.5) var counts := {} var encounters := 0 var rng := RandomNumberGenerator.new() for index in 1000: data.seed = index rng.seed = index SCENERY.generate(data,rng) assert(data.events.size() == 1) encounters += data.events.size() for spawned in data.events: assert(EVENTS.KINDS.has(spawned.kind)) counts[spawned.kind] = int(counts.get(spawned.kind,0))+1 assert(encounters == 1000 and counts.size() == 10) print("SURFACE EVENTS PASS: all 10 stories/actions, fixed offers, costs, discounts, scan stages, success/failure, evacuation, repair, safe return, destruction, one-time delivery. Spawn count / 1000: ",encounters,"; kinds: ",counts) get_tree().quit()