Randoloot/tests/levels/surface_events_test.gd
2026-10-18 14:46:34 +02:00

188 lines
8.2 KiB
GDScript

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()