class_name SurvivalModel extends RefCounted enum AlertLevel { NORMAL, CAUTION, RETURN, CRITICAL, EMPTY, } const DEFAULT_OXYGEN_SECONDS := 600.0 const DEFAULT_BATTERY := 100.0 const DEFAULT_HULL := 100.0 const DEFAULT_NOMINAL_DEPTH := 60.0 const DEFAULT_CRITICAL_DEPTH := 72.0 const DEFAULT_CRUSH_DEPTH := 78.0 var oxygen_max := DEFAULT_OXYGEN_SECONDS var battery_max := DEFAULT_BATTERY var hull_max := DEFAULT_HULL var oxygen := DEFAULT_OXYGEN_SECONDS var battery := DEFAULT_BATTERY var hull := DEFAULT_HULL var nominal_depth := DEFAULT_NOMINAL_DEPTH var critical_depth := DEFAULT_CRITICAL_DEPTH var crush_depth := DEFAULT_CRUSH_DEPTH var oxygen_drain_per_second := 1.0 var battery_drain_per_second_at_full_thrust := 0.60 func reset() -> void: oxygen = oxygen_max battery = battery_max hull = hull_max func tick(delta: float, thrust_ratio: float, depth: float, collision_damage: float = 0.0) -> Dictionary: if delta <= 0.0: return snapshot(depth) oxygen = maxf(0.0, oxygen - oxygen_drain_per_second * delta) battery = maxf( 0.0, battery - battery_drain_per_second_at_full_thrust * clampf(thrust_ratio, 0.0, 1.0) * delta ) if depth > nominal_depth: if depth >= crush_depth: hull -= 25.0 * delta else: var pressure_range := maxf(critical_depth - nominal_depth, 0.001) var pressure_ratio := clampf((depth - nominal_depth) / pressure_range, 0.0, 1.0) var damage_rate := lerpf(0.15, 5.0, pressure_ratio * pressure_ratio) if depth > critical_depth: var critical_ratio := (depth - critical_depth) / maxf(crush_depth - critical_depth, 0.001) damage_rate = lerpf(5.0, 18.0, clampf(critical_ratio, 0.0, 1.0)) hull -= damage_rate * delta hull = clampf(hull - maxf(collision_damage, 0.0), 0.0, hull_max) return snapshot(depth) func snapshot(depth: float) -> Dictionary: return { "oxygen": oxygen, "oxygen_max": oxygen_max, "oxygen_ratio": _ratio(oxygen, oxygen_max), "battery": battery, "battery_max": battery_max, "battery_ratio": _ratio(battery, battery_max), "hull": hull, "hull_max": hull_max, "hull_ratio": _ratio(hull, hull_max), "depth": maxf(depth, 0.0), "nominal_depth": nominal_depth, "critical_depth": critical_depth, "crush_depth": crush_depth, "oxygen_alert": alert_level(oxygen, oxygen_max), "battery_alert": alert_level(battery, battery_max), "hull_alert": alert_level(hull, hull_max), "depth_alert": depth_alert_level(depth), "loss_reason": loss_reason(), } func loss_reason() -> StringName: if hull <= 0.0: return &"hull_failure" if oxygen <= 0.0: return &"oxygen_depleted" return &"" func alert_level(value: float, maximum: float) -> AlertLevel: var ratio := _ratio(value, maximum) if ratio <= 0.0: return AlertLevel.EMPTY if ratio <= 0.05: return AlertLevel.CRITICAL if ratio <= 0.15: return AlertLevel.RETURN if ratio <= 0.30: return AlertLevel.CAUTION return AlertLevel.NORMAL func depth_alert_level(depth: float) -> AlertLevel: if depth >= crush_depth: return AlertLevel.EMPTY if depth >= critical_depth: return AlertLevel.CRITICAL if depth > nominal_depth: return AlertLevel.RETURN if depth >= nominal_depth * 0.85: return AlertLevel.CAUTION return AlertLevel.NORMAL func estimated_return_energy(distance: float, vertical_gain: float = 0.0) -> float: var seconds_at_cruise := maxf(distance, 0.0) / 6.0 var base_cost := seconds_at_cruise * battery_drain_per_second_at_full_thrust var vertical_cost := maxf(vertical_gain, 0.0) * 0.035 return base_cost + vertical_cost func force_critical_state() -> void: oxygen = minf(oxygen, 30.0) battery = minf(battery, 12.0) hull = minf(hull, 28.0) func _ratio(value: float, maximum: float) -> float: if maximum <= 0.0: return 0.0 return clampf(value / maximum, 0.0, 1.0)