diff --git a/.gitignore b/.gitignore
index 237f22268a5c24f4d81392e395d893b5af770b60..2f1f81d1ba05de2544aeb53d61d2a222b59de31f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,7 @@ __pycache__/
 env/
 build/
 develop-eggs/
-dist/
+# dist/
 downloads/
 eggs/
 .eggs/
@@ -117,3 +117,5 @@ images/test/
 test_save.dat
 
 .visualizations
+
+playground/
diff --git a/dist/flatland_rl-3.0.0-py2.py3-none-any.whl b/dist/flatland_rl-3.0.0-py2.py3-none-any.whl
new file mode 100644
index 0000000000000000000000000000000000000000..b6107580dba9207c9afc593269a366fa7daa97ce
Binary files /dev/null and b/dist/flatland_rl-3.0.0-py2.py3-none-any.whl differ
diff --git a/examples/introduction_flatland_2_1.py b/examples/introduction_flatland_3.py
similarity index 97%
rename from examples/introduction_flatland_2_1.py
rename to examples/introduction_flatland_3.py
index d770fa43ffee5a1a8e4231a8a6a2d9efe7891746..3bb8c2b1eab2c95a56ef0f54e0312740d1fff7df 100644
--- a/examples/introduction_flatland_2_1.py
+++ b/examples/introduction_flatland_3.py
@@ -12,7 +12,7 @@ from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_env import RailEnvActions
 from flatland.envs.rail_generators import sparse_rail_generator
 #from flatland.envs.sparse_rail_gen import SparseRailGen
-from flatland.envs.schedule_generators import sparse_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 # We also include a renderer because we want to visualize what is going on in the environment
 from flatland.utils.rendertools import RenderTool, AgentRenderVariant
 
@@ -45,7 +45,7 @@ rail_generator = sparse_rail_generator(max_num_cities=cities_in_map,
                                        seed=seed,
                                        grid_mode=grid_distribution_of_cities,
                                        max_rails_between_cities=max_rails_between_cities,
-                                       max_rails_in_city=max_rail_in_cities,
+                                       max_rail_pairs_in_city=max_rail_in_cities,
                                        )
 
 #rail_generator = SparseRailGen(max_num_cities=cities_in_map,
@@ -68,7 +68,7 @@ speed_ration_map = {1.: 0.25,  # Fast passenger train
 
 # We can now initiate the schedule generator with the given speed profiles
 
-schedule_generator = sparse_schedule_generator(speed_ration_map)
+line_generator = sparse_line_generator(speed_ration_map)
 
 # We can furthermore pass stochastic data to the RailEnv constructor which will allow for stochastic malfunctions
 # during an episode.
@@ -87,7 +87,7 @@ observation_builder = GlobalObsForRailEnv()
 env = RailEnv(width=width,
               height=height,
               rail_generator=rail_generator,
-              schedule_generator=schedule_generator,
+              line_generator=line_generator,
               number_of_agents=nr_trains,
               obs_builder_object=observation_builder,
               #malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
diff --git a/flatland/cli.py b/flatland/cli.py
index 2bd5cca2730772592f6a345d3328f8c6ae1d1df8..6dfc6c7de1a93afedf83e564d5962b588632b164 100644
--- a/flatland/cli.py
+++ b/flatland/cli.py
@@ -10,7 +10,7 @@ import redis
 
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import complex_rail_generator
-from flatland.envs.schedule_generators import complex_schedule_generator
+from flatland.envs.line_generators import complex_line_generator
 from flatland.evaluators.service import FlatlandRemoteEvaluationService
 from flatland.utils.rendertools import RenderTool
 
@@ -22,7 +22,7 @@ def demo(args=None):
         nr_start_goal=10,
         nr_extra=1,
         min_dist=8,
-        max_dist=99999), schedule_generator=complex_schedule_generator(), number_of_agents=5)
+        max_dist=99999), line_generator=complex_line_generator(), number_of_agents=5)
 
     env._max_episode_steps = int(15 * (env.width + env.height))
     env_renderer = RenderTool(env)
diff --git a/flatland/envs/agent_utils.py b/flatland/envs/agent_utils.py
index ef0701ac2abe2cc87e329a02120db6b1ca348584..fffe7ff786a32a6796af9667f1dfb9a3eb92ce9c 100644
--- a/flatland/envs/agent_utils.py
+++ b/flatland/envs/agent_utils.py
@@ -1,18 +1,21 @@
+from flatland.envs.rail_trainrun_data_structures import Waypoint
+import numpy as np
+
 from enum import IntEnum
 from itertools import starmap
-from typing import Tuple, Optional, NamedTuple
+from typing import Tuple, Optional, NamedTuple, List
 
-from attr import attrs, attrib, Factory
+from attr import attr, attrs, attrib, Factory
 
 from flatland.core.grid.grid4 import Grid4TransitionsEnum
-from flatland.envs.schedule_utils import Schedule
-
+from flatland.envs.timetable_utils import Line
 
 class RailAgentStatus(IntEnum):
-    READY_TO_DEPART = 0  # not in grid yet (position is None) -> prediction as if it were at initial position
-    ACTIVE = 1  # in grid (position is not None), not done -> prediction is remaining path
-    DONE = 2  # in grid (position is not None), but done -> prediction is stay at target forever
-    DONE_REMOVED = 3  # removed from grid (position is None) -> prediction is None
+    WAITING = 0
+    READY_TO_DEPART = 1  # not in grid yet (position is None) -> prediction as if it were at initial position
+    ACTIVE = 2  # in grid (position is not None), not done -> prediction is remaining path
+    DONE = 3  # in grid (position is not None), but done -> prediction is stay at target forever
+    DONE_REMOVED = 4  # removed from grid (position is None) -> prediction is None
 
 
 Agent = NamedTuple('Agent', [('initial_position', Tuple[int, int]),
@@ -20,23 +23,31 @@ Agent = NamedTuple('Agent', [('initial_position', Tuple[int, int]),
                              ('direction', Grid4TransitionsEnum),
                              ('target', Tuple[int, int]),
                              ('moving', bool),
+                             ('earliest_departure', int),
+                             ('latest_arrival', int),
                              ('speed_data', dict),
                              ('malfunction_data', dict),
                              ('handle', int),
                              ('status', RailAgentStatus),
                              ('position', Tuple[int, int]),
+                             ('arrival_time', int),
                              ('old_direction', Grid4TransitionsEnum),
                              ('old_position', Tuple[int, int])])
 
 
 @attrs
 class EnvAgent:
+    # INIT FROM HERE IN _from_line()
     initial_position = attrib(type=Tuple[int, int])
     initial_direction = attrib(type=Grid4TransitionsEnum)
     direction = attrib(type=Grid4TransitionsEnum)
     target = attrib(type=Tuple[int, int])
     moving = attrib(default=False, type=bool)
 
+    # NEW : EnvAgent - Schedule properties
+    earliest_departure = attrib(default=None, type=int)  # default None during _from_line()
+    latest_arrival = attrib(default=None, type=int)  # default None during _from_line()
+
     # speed_data: speed is added to position_fraction on each moving step, until position_fraction>=1.0,
     # after which 'transition_action_on_cellexit' is executed (equivalent to executing that action in the previous
     # cell if speed=1, as default)
@@ -52,22 +63,33 @@ class EnvAgent:
                           'moving_before_malfunction': False})))
 
     handle = attrib(default=None)
+    # INIT TILL HERE IN _from_line()
 
-    status = attrib(default=RailAgentStatus.READY_TO_DEPART, type=RailAgentStatus)
+    status = attrib(default=RailAgentStatus.WAITING, type=RailAgentStatus)
     position = attrib(default=None, type=Optional[Tuple[int, int]])
 
+    # NEW : EnvAgent Reward Handling
+    arrival_time = attrib(default=None, type=int)
+
     # used in rendering
     old_direction = attrib(default=None)
     old_position = attrib(default=None)
 
     def reset(self):
         """
-        Resets the agents to their initial values of the episode
+        Resets the agents to their initial values of the episode. Called after ScheduleTime generation.
         """
         self.position = None
         # TODO: set direction to None: https://gitlab.aicrowd.com/flatland/flatland/issues/280
         self.direction = self.initial_direction
-        self.status = RailAgentStatus.READY_TO_DEPART
+
+        if (self.earliest_departure == 0):
+            self.status = RailAgentStatus.READY_TO_DEPART
+        else:
+            self.status = RailAgentStatus.WAITING
+            
+        self.arrival_time = None
+
         self.old_position = None
         self.old_direction = None
         self.moving = False
@@ -81,39 +103,67 @@ class EnvAgent:
         self.malfunction_data['nr_malfunctions'] = 0
         self.malfunction_data['moving_before_malfunction'] = False
 
+    # NEW : Callables
+    def get_shortest_path(self, distance_map) -> List[Waypoint]:
+        from flatland.envs.rail_env_shortest_paths import get_shortest_paths # Circular dep fix
+        return get_shortest_paths(distance_map=distance_map, agent_handle=self.handle)[self.handle]
+        
+    def get_travel_time_on_shortest_path(self, distance_map) -> int:
+        shortest_path = self.get_shortest_path(distance_map)
+        if shortest_path is not None:
+            distance = len(shortest_path)
+        else:
+            distance = 0
+        speed = self.speed_data['speed']
+        return int(np.ceil(distance / speed))
+
+    def get_time_remaining_until_latest_arrival(self, elapsed_steps: int) -> int:
+        return self.latest_arrival - elapsed_steps
+
+    def get_current_delay(self, elapsed_steps: int, distance_map) -> int:
+        '''
+        +ve if arrival time is projected before latest arrival
+        -ve if arrival time is projected after latest arrival
+        '''
+        return self.get_time_remaining_until_latest_arrival(elapsed_steps) - \
+               self.get_travel_time_on_shortest_path(distance_map)
+
     def to_agent(self) -> Agent:
-        return Agent(initial_position=self.initial_position, initial_direction=self.initial_direction,
-                     direction=self.direction, target=self.target, moving=self.moving, speed_data=self.speed_data,
-                     malfunction_data=self.malfunction_data, handle=self.handle, status=self.status,
-                     position=self.position, old_direction=self.old_direction, old_position=self.old_position)
+        return Agent(initial_position=self.initial_position, initial_direction=self.initial_direction, 
+                     direction=self.direction, target=self.target, moving=self.moving, earliest_departure=self.earliest_departure, 
+                     latest_arrival=self.latest_arrival, speed_data=self.speed_data, malfunction_data=self.malfunction_data, 
+                     handle=self.handle, status=self.status, position=self.position, arrival_time=self.arrival_time, 
+                     old_direction=self.old_direction, old_position=self.old_position)
 
     @classmethod
-    def from_schedule(cls, schedule: Schedule):
+    def from_line(cls, line: Line):
         """ Create a list of EnvAgent from lists of positions, directions and targets
         """
         speed_datas = []
 
-        for i in range(len(schedule.agent_positions)):
+        for i in range(len(line.agent_positions)):
             speed_datas.append({'position_fraction': 0.0,
-                                'speed': schedule.agent_speeds[i] if schedule.agent_speeds is not None else 1.0,
+                                'speed': line.agent_speeds[i] if line.agent_speeds is not None else 1.0,
                                 'transition_action_on_cellexit': 0})
 
         malfunction_datas = []
-        for i in range(len(schedule.agent_positions)):
+        for i in range(len(line.agent_positions)):
             malfunction_datas.append({'malfunction': 0,
-                                      'malfunction_rate': schedule.agent_malfunction_rates[
-                                          i] if schedule.agent_malfunction_rates is not None else 0.,
+                                      'malfunction_rate': line.agent_malfunction_rates[
+                                          i] if line.agent_malfunction_rates is not None else 0.,
                                       'next_malfunction': 0,
                                       'nr_malfunctions': 0})
 
-        return list(starmap(EnvAgent, zip(schedule.agent_positions,
-                                          schedule.agent_directions,
-                                          schedule.agent_directions,
-                                          schedule.agent_targets,
-                                          [False] * len(schedule.agent_positions),
+        return list(starmap(EnvAgent, zip(line.agent_positions,
+                                          line.agent_directions,
+                                          line.agent_directions,
+                                          line.agent_targets, 
+                                          [False] * len(line.agent_positions), 
+                                          [None] * len(line.agent_positions), # earliest_departure
+                                          [None] * len(line.agent_positions), # latest_arrival
                                           speed_datas,
                                           malfunction_datas,
-                                          range(len(schedule.agent_positions)))))
+                                          range(len(line.agent_positions)))))
 
     @classmethod
     def load_legacy_static_agent(cls, static_agents_data: Tuple):
diff --git a/flatland/envs/line_generators.py b/flatland/envs/line_generators.py
new file mode 100644
index 0000000000000000000000000000000000000000..74d01e6f23856e9f14d2fbe70eb2bdbfb85175be
--- /dev/null
+++ b/flatland/envs/line_generators.py
@@ -0,0 +1,201 @@
+"""Line generators (railway undertaking, "EVU")."""
+import warnings
+from typing import Tuple, List, Callable, Mapping, Optional, Any
+
+import numpy as np
+from numpy.random.mtrand import RandomState
+
+from flatland.core.grid.grid4_utils import get_new_position
+from flatland.core.transition_map import GridTransitionMap
+from flatland.envs.agent_utils import EnvAgent
+from flatland.envs.timetable_utils import Line
+from flatland.envs import persistence
+
+AgentPosition = Tuple[int, int]
+LineGenerator = Callable[[GridTransitionMap, int, Optional[Any], Optional[int]], Line]
+
+
+def speed_initialization_helper(nb_agents: int, speed_ratio_map: Mapping[float, float] = None,
+                                seed: int = None, np_random: RandomState = None) -> List[float]:
+    """
+    Parameters
+    ----------
+    nb_agents : int
+        The number of agents to generate a speed for
+    speed_ratio_map : Mapping[float,float]
+        A map of speeds mappint to their ratio of appearance. The ratios must sum up to 1.
+
+    Returns
+    -------
+    List[float]
+        A list of size nb_agents of speeds with the corresponding probabilistic ratios.
+    """
+    if speed_ratio_map is None:
+        return [1.0] * nb_agents
+
+    nb_classes = len(speed_ratio_map.keys())
+    speed_ratio_map_as_list: List[Tuple[float, float]] = list(speed_ratio_map.items())
+    speed_ratios = list(map(lambda t: t[1], speed_ratio_map_as_list))
+    speeds = list(map(lambda t: t[0], speed_ratio_map_as_list))
+    return list(map(lambda index: speeds[index], np_random.choice(nb_classes, nb_agents, p=speed_ratios)))
+
+
+class BaseLineGen(object):
+    def __init__(self, speed_ratio_map: Mapping[float, float] = None, seed: int = 1):
+        self.speed_ratio_map = speed_ratio_map
+        self.seed = seed
+
+    def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any=None, num_resets: int = 0,
+        np_random: RandomState = None) -> Line:
+        pass
+
+    def __call__(self, *args, **kwargs):
+        return self.generate(*args, **kwargs)
+
+
+def sparse_line_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> LineGenerator:
+    return SparseLineGen(speed_ratio_map, seed)
+
+
+class SparseLineGen(BaseLineGen):
+    """
+
+    This is the line generator which is used for Round 2 of the Flatland challenge. It produces lines
+    to railway networks provided by sparse_rail_generator.
+    :param speed_ratio_map: Speed ratios of all agents. They are probabilities of all different speeds and have to
+            add up to 1.
+    :param seed: Initiate random seed generator
+    """
+
+    def generate(self, rail: GridTransitionMap, num_agents: int, hints: dict, num_resets: int,
+                  np_random: RandomState) -> Line:
+        """
+
+        The generator that assigns tasks to all the agents
+        :param rail: Rail infrastructure given by the rail_generator
+        :param num_agents: Number of agents to include in the line
+        :param hints: Hints provided by the rail_generator These include positions of start/target positions
+        :param num_resets: How often the generator has been reset.
+        :return: Returns the generator to the rail constructor
+        """
+
+        _runtime_seed = self.seed + num_resets
+
+        train_stations = hints['train_stations']
+        city_positions = hints['city_positions']
+        city_orientation = hints['city_orientations']
+        max_num_agents = hints['num_agents']
+        city_orientations = hints['city_orientations']
+        if num_agents > max_num_agents:
+            num_agents = max_num_agents
+            warnings.warn("Too many agents! Changes number of agents.")
+        # Place agents and targets within available train stations
+        agents_position = []
+        agents_target = []
+        agents_direction = []
+
+
+        city1, city2 = None, None
+        city1_num_stations, city2_num_stations = None, None
+        city1_possible_orientations, city2_possible_orientations = None, None
+
+
+        for agent_idx in range(num_agents):
+
+            if (agent_idx % 2 == 0):
+                # Setlect 2 cities, find their num_stations and possible orientations
+                city_idx = np_random.choice(len(city_positions), 2, replace=False)
+                city1 = city_idx[0]
+                city2 = city_idx[1]
+                city1_num_stations = len(train_stations[city1])
+                city2_num_stations = len(train_stations[city2])
+                city1_possible_orientations = [city_orientation[city1],
+                                                (city_orientation[city1] + 2) % 4]
+                city2_possible_orientations = [city_orientation[city2],
+                                                (city_orientation[city2] + 2) % 4]
+
+                # Agent 1 : city1 > city2, Agent 2: city2 > city1
+                agent_start_idx = ((2 * np_random.randint(0, 10))) % city1_num_stations
+                agent_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city2_num_stations
+
+                agent_start = train_stations[city1][agent_start_idx]
+                agent_target = train_stations[city2][agent_target_idx]
+
+                agent_orientation = np_random.choice(city1_possible_orientations)
+
+
+            else:
+                agent_start_idx = ((2 * np_random.randint(0, 10))) % city2_num_stations
+                agent_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city1_num_stations
+                
+                agent_start = train_stations[city2][agent_start_idx]
+                agent_target = train_stations[city1][agent_target_idx]
+                            
+                agent_orientation = np_random.choice(city2_possible_orientations)
+
+            
+            # agent1 details
+            agents_position.append((agent_start[0][0], agent_start[0][1]))
+            agents_target.append((agent_target[0][0], agent_target[0][1]))
+            agents_direction.append(agent_orientation)
+
+
+        if self.speed_ratio_map:
+            speeds = speed_initialization_helper(num_agents, self.speed_ratio_map, seed=_runtime_seed, np_random=np_random)
+        else:
+            speeds = [1.0] * len(agents_position)
+
+        # We add multiply factors to the max number of time steps to simplify task in Flatland challenge.
+        # These factors might change in the future.
+        timedelay_factor = 4
+        alpha = 2
+        max_episode_steps = int(
+            timedelay_factor * alpha * (rail.width + rail.height + num_agents / len(city_positions)))
+
+        return Line(agent_positions=agents_position, agent_directions=agents_direction,
+                        agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None)
+
+
+def line_from_file(filename, load_from_package=None) -> LineGenerator:
+    """
+    Utility to load pickle file
+
+    Parameters
+    ----------
+    input_file : Pickle file generated by env.save() or editor
+
+    Returns
+    -------
+    Tuple[List[Tuple[int,int]], List[Tuple[int,int]], List[Tuple[int,int]], List[float]]
+        initial positions, directions, targets speeds
+    """
+
+    def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0,
+                  np_random: RandomState = None) -> Line:
+
+        env_dict = persistence.RailEnvPersister.load_env_dict(filename, load_from_package=load_from_package)
+
+        max_episode_steps = env_dict.get("max_episode_steps", 0)
+        if (max_episode_steps==0):
+            print("This env file has no max_episode_steps (deprecated) - setting to 100")
+            max_episode_steps = 100
+            
+        agents = env_dict["agents"]
+        
+        # setup with loaded data
+        agents_position = [a.initial_position for a in agents]
+
+        # this logic is wrong - we should really load the initial_direction as the direction.
+        #agents_direction = [a.direction for a in agents]
+        agents_direction = [a.initial_direction for a in agents]
+        agents_target = [a.target for a in agents]
+        agents_speed = [a.speed_data['speed'] for a in agents]
+
+        # Malfunctions from here are not used.  They have their own generator.
+        #agents_malfunction = [a.malfunction_data['malfunction_rate'] for a in agents]
+
+        return Line(agent_positions=agents_position, agent_directions=agents_direction,
+                        agent_targets=agents_target, agent_speeds=agents_speed, 
+                        agent_malfunction_rates=None)
+
+    return generator
diff --git a/flatland/envs/observations.py b/flatland/envs/observations.py
index beabe4a4643615290d2871b5da0d1589caa4dd42..4de36060f2864f6f33cfefd8ac46816da566dbc6 100644
--- a/flatland/envs/observations.py
+++ b/flatland/envs/observations.py
@@ -101,10 +101,13 @@ class TreeObsForRailEnv(ObservationBuilder):
                 self.location_has_agent_malfunction[tuple(_agent.position)] = _agent.malfunction_data[
                     'malfunction']
 
-            if _agent.status in [RailAgentStatus.READY_TO_DEPART] and \
+            # [NIMISH] WHAT IS THIS
+            if _agent.status in [RailAgentStatus.READY_TO_DEPART, RailAgentStatus.WAITING] and \
                 _agent.initial_position:
-                self.location_has_agent_ready_to_depart[tuple(_agent.initial_position)] = \
-                    self.location_has_agent_ready_to_depart.get(tuple(_agent.initial_position), 0) + 1
+                    self.location_has_agent_ready_to_depart.setdefault(tuple(_agent.initial_position), 0)
+                    self.location_has_agent_ready_to_depart[tuple(_agent.initial_position)] += 1
+                # self.location_has_agent_ready_to_depart[tuple(_agent.initial_position)] = \
+                #     self.location_has_agent_ready_to_depart.get(tuple(_agent.initial_position), 0) + 1
 
         observations = super().get_many(handles)
 
@@ -192,8 +195,10 @@ class TreeObsForRailEnv(ObservationBuilder):
         if handle > len(self.env.agents):
             print("ERROR: obs _get - handle ", handle, " len(agents)", len(self.env.agents))
         agent = self.env.agents[handle]  # TODO: handle being treated as index
-
-        if agent.status == RailAgentStatus.READY_TO_DEPART:
+        
+        if agent.status == RailAgentStatus.WAITING:
+            agent_virtual_position = agent.initial_position
+        elif agent.status == RailAgentStatus.READY_TO_DEPART:
             agent_virtual_position = agent.initial_position
         elif agent.status == RailAgentStatus.ACTIVE:
             agent_virtual_position = agent.position
@@ -564,7 +569,9 @@ class GlobalObsForRailEnv(ObservationBuilder):
     def get(self, handle: int = 0) -> (np.ndarray, np.ndarray, np.ndarray):
 
         agent = self.env.agents[handle]
-        if agent.status == RailAgentStatus.READY_TO_DEPART:
+        if agent.status == RailAgentStatus.WAITING:
+            agent_virtual_position = agent.initial_position
+        elif agent.status == RailAgentStatus.READY_TO_DEPART:
             agent_virtual_position = agent.initial_position
         elif agent.status == RailAgentStatus.ACTIVE:
             agent_virtual_position = agent.position
@@ -602,7 +609,7 @@ class GlobalObsForRailEnv(ObservationBuilder):
                 obs_agents_state[other_agent.position][2] = other_agent.malfunction_data['malfunction']
                 obs_agents_state[other_agent.position][3] = other_agent.speed_data['speed']
             # fifth channel: all ready to depart on this position
-            if other_agent.status == RailAgentStatus.READY_TO_DEPART:
+            if other_agent.status == RailAgentStatus.READY_TO_DEPART or other_agent.status == RailAgentStatus.WAITING:
                 obs_agents_state[other_agent.initial_position][4] += 1
         return self.rail_obs, obs_agents_state, obs_targets
 
diff --git a/flatland/envs/persistence.py b/flatland/envs/persistence.py
index bc4b169b1aad3893d96d82cb8284b369f13104f2..41f352e70017f1f37bb66abaa911d25725618836 100644
--- a/flatland/envs/persistence.py
+++ b/flatland/envs/persistence.py
@@ -21,7 +21,7 @@ from flatland.envs.distance_map import DistanceMap
 # cannot import objects / classes directly because of circular import
 from flatland.envs import malfunction_generators as mal_gen
 from flatland.envs import rail_generators as rail_gen
-from flatland.envs import schedule_generators as sched_gen
+from flatland.envs import line_generators as line_gen
 
 msgpack_numpy.patch()
 
@@ -122,7 +122,7 @@ class RailEnvPersister(object):
                 width=width, height=height,
                 rail_generator=rail_gen.rail_from_file(filename, 
                     load_from_package=load_from_package),
-                schedule_generator=sched_gen.schedule_from_file(filename,
+                    line_generator=line_gen.line_from_file(filename,
                     load_from_package=load_from_package),
                 #malfunction_generator_and_process_data=mal_gen.malfunction_from_file(filename,
                 #    load_from_package=load_from_package),
@@ -163,7 +163,7 @@ class RailEnvPersister(object):
             # remove the legacy key
             del env_dict["agents_static"]
         elif "agents" in env_dict:
-            env_dict["agents"] = [EnvAgent(*d[0:12]) for d in env_dict["agents"]]
+            env_dict["agents"] = [EnvAgent(*d[0:len(d)]) for d in env_dict["agents"]]
 
         return env_dict
 
diff --git a/flatland/envs/predictions.py b/flatland/envs/predictions.py
index c2d342d6b43a445c1deb93dc59476478875bc786..3cd3b71443b33398a8cc02bfec8bf51c682238ef 100644
--- a/flatland/envs/predictions.py
+++ b/flatland/envs/predictions.py
@@ -7,7 +7,7 @@ import numpy as np
 from flatland.core.env_prediction_builder import PredictionBuilder
 from flatland.envs.agent_utils import RailAgentStatus
 from flatland.envs.distance_map import DistanceMap
-from flatland.envs.rail_env import RailEnvActions
+from flatland.envs.rail_env_action import RailEnvActions
 from flatland.envs.rail_env_shortest_paths import get_shortest_paths
 from flatland.utils.ordered_set import OrderedSet
 
@@ -126,8 +126,9 @@ class ShortestPathPredictorForRailEnv(PredictionBuilder):
 
         prediction_dict = {}
         for agent in agents:
-
-            if agent.status == RailAgentStatus.READY_TO_DEPART:
+            if agent.status == RailAgentStatus.WAITING:
+                agent_virtual_position = agent.initial_position
+            elif agent.status == RailAgentStatus.READY_TO_DEPART:
                 agent_virtual_position = agent.initial_position
             elif agent.status == RailAgentStatus.ACTIVE:
                 agent_virtual_position = agent.position
diff --git a/flatland/envs/rail_env.py b/flatland/envs/rail_env.py
index 94d911eaab8dfe545d6960f9cc7068e53fee8e16..ab0e14879a354993331519315d7f1fbc423f2af2 100644
--- a/flatland/envs/rail_env.py
+++ b/flatland/envs/rail_env.py
@@ -17,11 +17,13 @@ from flatland.core.grid.grid_utils import IntVector2D
 from flatland.core.transition_map import GridTransitionMap
 from flatland.envs.agent_utils import EnvAgent, RailAgentStatus
 from flatland.envs.distance_map import DistanceMap
+from flatland.envs.rail_env_action import RailEnvActions
 
 # Need to use circular imports for persistence.
 from flatland.envs import malfunction_generators as mal_gen
 from flatland.envs import rail_generators as rail_gen
-from flatland.envs import schedule_generators as sched_gen
+from flatland.envs import line_generators as line_gen
+from flatland.envs.timetable_generators import timetable_generator
 from flatland.envs import persistence
 from flatland.envs import agent_chains as ac
 
@@ -32,7 +34,7 @@ from gym.utils import seeding
 # from flatland.envs.malfunction_generators import no_malfunction_generator, Malfunction, MalfunctionProcessData
 # from flatland.envs.observations import GlobalObsForRailEnv
 # from flatland.envs.rail_generators import random_rail_generator, RailGenerator
-# from flatland.envs.schedule_generators import random_schedule_generator, ScheduleGenerator
+# from flatland.envs.line_generators import random_line_generator, LineGenerator
 
 
 
@@ -66,28 +68,6 @@ def fast_count_nonzero(possible_transitions: (int, int, int, int)):
     return possible_transitions[0] + possible_transitions[1] + possible_transitions[2] + possible_transitions[3]
 
 
-class RailEnvActions(IntEnum):
-    DO_NOTHING = 0  # implies change of direction in a dead-end!
-    MOVE_LEFT = 1
-    MOVE_FORWARD = 2
-    MOVE_RIGHT = 3
-    STOP_MOVING = 4
-
-    @staticmethod
-    def to_char(a: int):
-        return {
-            0: 'B',
-            1: 'L',
-            2: 'F',
-            3: 'R',
-            4: 'S',
-        }[a]
-
-
-RailEnvGridPos = NamedTuple('RailEnvGridPos', [('r', int), ('c', int)])
-RailEnvNextAction = NamedTuple('RailEnvNextAction', [('action', RailEnvActions), ('next_position', RailEnvGridPos),
-                                                     ('next_direction', Grid4TransitionsEnum)])
-
 
 class RailEnv(Environment):
     """
@@ -141,22 +121,25 @@ class RailEnv(Environment):
     For Round 2, they will be passed to the constructor as arguments, to allow for more flexibility.
 
     """
-    alpha = 1.0
-    beta = 1.0
     # Epsilon to avoid rounding errors
     epsilon = 0.01
-    invalid_action_penalty = 0  # previously -2; GIACOMO: we decided that invalid actions will carry no penalty
+    # NEW : REW: Sparse Reward
+    alpha = 0
+    beta = 0
     step_penalty = -1 * alpha
     global_reward = 1 * beta
+    invalid_action_penalty = 0  # previously -2; GIACOMO: we decided that invalid actions will carry no penalty
     stop_penalty = 0  # penalty for stopping a moving agent
     start_penalty = 0  # penalty for starting a stopped agent
+    cancellation_factor = 1
+    cancellation_time_buffer = 0
 
     def __init__(self,
                  width,
                  height,
                  rail_generator=None,
-                 schedule_generator=None,  # : sched_gen.ScheduleGenerator = sched_gen.random_schedule_generator(),
-                 number_of_agents=1,
+                 line_generator=None,  # : line_gen.LineGenerator = line_gen.random_line_generator(),
+                 number_of_agents=2,
                  obs_builder_object: ObservationBuilder = GlobalObsForRailEnv(),
                  malfunction_generator_and_process_data=None,  # mal_gen.no_malfunction_generator(),
                  malfunction_generator=None,
@@ -175,12 +158,12 @@ class RailEnv(Environment):
             height and agents handles of a  rail environment, along with the number of times
             the env has been reset, and returns a GridTransitionMap object and a list of
             starting positions, targets, and initial orientations for agent handle.
-            The rail_generator can pass a distance map in the hints or information for specific schedule_generators.
+            The rail_generator can pass a distance map in the hints or information for specific line_generators.
             Implementations can be found in flatland/envs/rail_generators.py
-        schedule_generator : function
-            The schedule_generator function is a function that takes the grid, the number of agents and optional hints
+        line_generator : function
+            The line_generator function is a function that takes the grid, the number of agents and optional hints
             and returns a list of starting positions, targets, initial orientations and speed for all agent handles.
-            Implementations can be found in flatland/envs/schedule_generators.py
+            Implementations can be found in flatland/envs/line_generators.py
         width : int
             The width of the rail map. Potentially in the future,
             a range of widths to sample from.
@@ -214,15 +197,16 @@ class RailEnv(Environment):
         else:
             self.malfunction_generator = mal_gen.NoMalfunctionGen()
             self.malfunction_process_data = self.malfunction_generator.get_process_data()
+        
+        self.number_of_agents = number_of_agents
 
         # self.rail_generator: RailGenerator = rail_generator
         if rail_generator is None:
-            rail_generator = rail_gen.random_rail_generator()
+            rail_generator = rail_gen.sparse_rail_generator()
         self.rail_generator = rail_generator
-        # self.schedule_generator: ScheduleGenerator = schedule_generator
-        if schedule_generator is None:
-            schedule_generator = sched_gen.random_schedule_generator()
-        self.schedule_generator = schedule_generator
+        if line_generator is None:
+            line_generator = line_gen.sparse_line_generator()
+        self.line_generator = line_generator
 
         self.rail: Optional[GridTransitionMap] = None
         self.width = width
@@ -246,7 +230,6 @@ class RailEnv(Environment):
         self.dev_pred_dict = {}
 
         self.agents: List[EnvAgent] = []
-        self.number_of_agents = number_of_agents
         self.num_resets = 0
         self.distance_map = DistanceMap(self.agents, self.height, self.width)
 
@@ -292,7 +275,7 @@ class RailEnv(Environment):
         return len(self.agents) - 1
 
     def set_agent_active(self, agent: EnvAgent):
-        if agent.status == RailAgentStatus.READY_TO_DEPART and self.cell_free(agent.initial_position):
+        if agent.status == RailAgentStatus.READY_TO_DEPART or agent.status == RailAgentStatus.WAITING and self.cell_free(agent.initial_position): ## Dipam : Why is this code even there???
             agent.status = RailAgentStatus.ACTIVE
             self._set_agent_to_initial_position(agent, agent.initial_position)
 
@@ -378,17 +361,29 @@ class RailEnv(Environment):
             if optionals and 'agents_hints' in optionals:
                 agents_hints = optionals['agents_hints']
 
-            schedule = self.schedule_generator(self.rail, self.number_of_agents, agents_hints, self.num_resets,
-                                               self.np_random)
-            self.agents = EnvAgent.from_schedule(schedule)
+            line = self.line_generator(self.rail, self.number_of_agents, agents_hints, 
+                                               self.num_resets, self.np_random)
+            self.agents = EnvAgent.from_line(line)
 
-            # Get max number of allowed time steps from schedule generator
-            # Look at the specific schedule generator used to see where this number comes from
-            self._max_episode_steps = schedule.max_episode_steps
+            # Reset distance map - basically initializing
+            self.distance_map.reset(self.agents, self.rail)
 
-        self.agent_positions = np.zeros((self.height, self.width), dtype=int) - 1
+            # NEW : Time Schedule Generation
+            timetable = timetable_generator(self.agents, self.distance_map, 
+                                               agents_hints, self.np_random)
+
+            self._max_episode_steps = timetable.max_episode_steps
+
+            for agent_i, agent in enumerate(self.agents):
+                agent.earliest_departure = timetable.earliest_departures[agent_i]         
+                agent.latest_arrival = timetable.latest_arrivals[agent_i]
+        else:
+            self.distance_map.reset(self.agents, self.rail)
 
-        # Reset agents to initial
+        # Agent Positions Map
+        self.agent_positions = np.zeros((self.height, self.width), dtype=int) - 1
+        
+        # Reset agents to initial states
         self.reset_agents()
 
         for agent in self.agents:
@@ -412,7 +407,6 @@ class RailEnv(Environment):
 
         # Reset the state of the observation builder with the new environment
         self.obs_builder.reset()
-        self.distance_map.reset(self.agents, self.rail)
 
         # Reset the malfunction generator
         if "generate" in dir(self.malfunction_generator):
@@ -494,21 +488,7 @@ class RailEnv(Environment):
 
         # If we're done, set reward and info_dict and step() is done.
         if self.dones["__all__"]:
-            self.rewards_dict = {}
-            info_dict = {
-                "action_required": {},
-                "malfunction": {},
-                "speed": {},
-                "status": {},
-            }
-            for i_agent, agent in enumerate(self.agents):
-                self.rewards_dict[i_agent] = self.global_reward
-                info_dict["action_required"][i_agent] = False
-                info_dict["malfunction"][i_agent] = 0
-                info_dict["speed"][i_agent] = 0
-                info_dict["status"][i_agent] = agent.status
-
-            return self._get_observations(), self.rewards_dict, self.dones, info_dict
+            raise Exception("Episode is done, cannot call step()")
 
         # Reset the step rewards
         self.rewards_dict = dict()
@@ -576,14 +556,40 @@ class RailEnv(Environment):
                 # Fix agents that finished their malfunction such that they can perform an action in the next step
                 self._fix_agent_after_malfunction(agent)
 
-        # Check for end of episode + set global reward to all rewards!
-        if have_all_agents_ended:
-            self.dones["__all__"] = True
-            self.rewards_dict = {i: self.global_reward for i in range(self.get_num_agents())}
-        if (self._max_episode_steps is not None) and (self._elapsed_steps >= self._max_episode_steps):
-            self.dones["__all__"] = True
-            for i_agent in range(self.get_num_agents()):
+        
+        # NEW : REW: (END)
+        if ((self._max_episode_steps is not None) and (self._elapsed_steps >= self._max_episode_steps)) \
+            or have_all_agents_ended :
+            
+            for i_agent, agent in enumerate(self.agents):
+                
+                # agent done? (arrival_time is not None)
+                if agent.status == RailAgentStatus.DONE or agent.status == RailAgentStatus.DONE_REMOVED:
+                    
+                    # if agent arrived earlier or on time = 0
+                    # if agent arrived later = -ve reward based on how late
+                    reward = min(agent.latest_arrival - agent.arrival_time, 0)
+                    self.rewards_dict[i_agent] += reward
+                
+                # Agents not done (arrival_time is None)
+                else:
+                    
+                    # CANCELLED check (never departed)
+                    if (agent.status == RailAgentStatus.READY_TO_DEPART):
+                        reward = -1 * self.cancellation_factor * \
+                            (agent.get_travel_time_on_shortest_path(self.distance_map) + 0) # 0 replaced with buffer
+                        self.rewards_dict[i_agent] += reward
+
+                    # Departed but never reached
+                    if (agent.status == RailAgentStatus.ACTIVE):
+                        reward = agent.get_current_delay(self._elapsed_steps, self.distance_map)
+                        self.rewards_dict[i_agent] += reward
+                
                 self.dones[i_agent] = True
+
+            self.dones["__all__"] = True
+        
+
         if self.record_steps:
             self.record_timestep(action_dict_)
 
@@ -734,6 +740,13 @@ class RailEnv(Environment):
         if agent.status in [RailAgentStatus.DONE, RailAgentStatus.DONE_REMOVED]:  # this agent has already completed...
             return
 
+        # NEW : STEP: WAITING > WAITING or WAITING > READY_TO_DEPART
+        if (agent.status == RailAgentStatus.WAITING):
+            if ( self._elapsed_steps >= agent.earliest_departure ):
+                agent.status = RailAgentStatus.READY_TO_DEPART
+            self.motionCheck.addAgent(i_agent, None, None)
+            return
+
         # agent gets active by a MOVE_* action and if c
         if agent.status == RailAgentStatus.READY_TO_DEPART:
             is_action_starting = action in [
@@ -844,7 +857,8 @@ class RailEnv(Environment):
     def _step_agent2_cf(self, i_agent):
         agent = self.agents[i_agent]
 
-        if agent.status in [RailAgentStatus.DONE, RailAgentStatus.DONE_REMOVED]:
+        # NEW : REW: (WAITING) no reward during WAITING...
+        if agent.status in [RailAgentStatus.DONE, RailAgentStatus.DONE_REMOVED, RailAgentStatus.WAITING]:
             return
 
         (move, rc_next) = self.motionCheck.check_motion(i_agent, agent.position)
@@ -885,18 +899,29 @@ class RailEnv(Environment):
                 agent.direction = new_direction
                 agent.speed_data['position_fraction'] = 0.0
 
+            # NEW : STEP: Check DONE  before / after LA & Check if RUNNING before / after LA
             # has the agent reached its target?
             if np.equal(agent.position, agent.target).all():
+                # arrived before or after Latest Arrival
                 agent.status = RailAgentStatus.DONE
                 self.dones[i_agent] = True
                 self.active_agents.remove(i_agent)
                 agent.moving = False
+                agent.arrival_time = self._elapsed_steps
                 self._remove_agent_from_scene(agent)
-            else:
-                self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed']
+
+            else: # not reached its target and moving
+                # running before Latest Arrival
+                if (self._elapsed_steps <= agent.latest_arrival):
+                    self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed']
+                else: # running after Latest Arrival
+                    self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] # + # NEGATIVE REWARD? per step?
         else:
-            # step penalty if not moving (stopped now or before)
-            self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed']
+            # stopped (!move) before Latest Arrival
+            if (self._elapsed_steps <= agent.latest_arrival):
+                self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed']
+            else:  # stopped (!move) after Latest Arrival
+                self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed']  # + # NEGATIVE REWARD? per step?
 
     def _set_agent_to_initial_position(self, agent: EnvAgent, new_position: IntVector2D):
         """
diff --git a/flatland/envs/rail_env_action.py b/flatland/envs/rail_env_action.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fcc175e7f7f63653153f8841ec3ba398876d4a1
--- /dev/null
+++ b/flatland/envs/rail_env_action.py
@@ -0,0 +1,25 @@
+from enum import IntEnum
+from typing import NamedTuple
+from flatland.core.grid.grid4 import Grid4TransitionsEnum
+
+class RailEnvActions(IntEnum):
+    DO_NOTHING = 0  # implies change of direction in a dead-end!
+    MOVE_LEFT = 1
+    MOVE_FORWARD = 2
+    MOVE_RIGHT = 3
+    STOP_MOVING = 4
+
+    @staticmethod
+    def to_char(a: int):
+        return {
+            0: 'B',
+            1: 'L',
+            2: 'F',
+            3: 'R',
+            4: 'S',
+        }[a]
+
+
+RailEnvGridPos = NamedTuple('RailEnvGridPos', [('r', int), ('c', int)])
+RailEnvNextAction = NamedTuple('RailEnvNextAction', [('action', RailEnvActions), ('next_position', RailEnvGridPos),
+                                                     ('next_direction', Grid4TransitionsEnum)])
diff --git a/flatland/envs/rail_env_shortest_paths.py b/flatland/envs/rail_env_shortest_paths.py
index 6bfb4bb558f135388b41ee2b830f74984e62eddc..8c9817781a5e50d1a02b4d39e0f604e8b854afb9 100644
--- a/flatland/envs/rail_env_shortest_paths.py
+++ b/flatland/envs/rail_env_shortest_paths.py
@@ -9,7 +9,7 @@ from flatland.core.grid.grid4_utils import get_new_position
 from flatland.core.transition_map import GridTransitionMap
 from flatland.envs.agent_utils import RailAgentStatus
 from flatland.envs.distance_map import DistanceMap
-from flatland.envs.rail_env import RailEnvNextAction, RailEnvActions, RailEnv
+from flatland.envs.rail_env_action import RailEnvActions, RailEnvNextAction
 from flatland.envs.rail_trainrun_data_structures import Waypoint
 from flatland.utils.ordered_set import OrderedSet
 
@@ -227,7 +227,9 @@ def get_shortest_paths(distance_map: DistanceMap, max_depth: Optional[int] = Non
     shortest_paths = dict()
 
     def _shortest_path_for_agent(agent):
-        if agent.status == RailAgentStatus.READY_TO_DEPART:
+        if agent.status == RailAgentStatus.WAITING:
+            position = agent.initial_position
+        elif agent.status == RailAgentStatus.READY_TO_DEPART:
             position = agent.initial_position
         elif agent.status == RailAgentStatus.ACTIVE:
             position = agent.position
@@ -274,7 +276,7 @@ def get_shortest_paths(distance_map: DistanceMap, max_depth: Optional[int] = Non
     return shortest_paths
 
 
-def get_k_shortest_paths(env: RailEnv,
+def get_k_shortest_paths(env,
                          source_position: Tuple[int, int],
                          source_direction: int,
                          target_position=Tuple[int, int],
diff --git a/flatland/envs/rail_env_utils.py b/flatland/envs/rail_env_utils.py
index 9143a86334401889fbd9d2494b2f97a8e6ef5435..22f73f98dddf01a9e34b55b62ea1d81c69d69713 100644
--- a/flatland/envs/rail_env_utils.py
+++ b/flatland/envs/rail_env_utils.py
@@ -3,7 +3,7 @@ from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_file
-from flatland.envs.schedule_generators import schedule_from_file
+from flatland.envs.line_generators import line_from_file
 
 
 def load_flatland_environment_from_file(file_name: str,
@@ -33,7 +33,7 @@ def load_flatland_environment_from_file(file_name: str,
             max_depth=2,
             predictor=ShortestPathPredictorForRailEnv(max_depth=10))
     environment = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name, load_from_package),
-                          schedule_generator=schedule_from_file(file_name, load_from_package),
+                          line_generator=line_from_file(file_name, load_from_package),
                           number_of_agents=1,
                           obs_builder_object=obs_builder_object,
                           record_steps=record_steps,
diff --git a/flatland/envs/rail_generators.py b/flatland/envs/rail_generators.py
index 5e2e9b1effcb9a4a5dc3247c606387e02b6c5e0c..08d2df07431fc8116c2713417a76a963a6e20489 100644
--- a/flatland/envs/rail_generators.py
+++ b/flatland/envs/rail_generators.py
@@ -67,147 +67,6 @@ class EmptyRailGen(RailGen):
         return grid_map, None
 
 
-
-def complex_rail_generator(nr_start_goal=1,
-                           nr_extra=100,
-                           min_dist=20,
-                           max_dist=99999,
-                           seed=1) -> RailGenerator:
-    """
-    complex_rail_generator
-
-    Parameters
-    ----------
-    width : int
-        The width (number of cells) of the grid to generate.
-    height : int
-        The height (number of cells) of the grid to generate.
-
-    Returns
-    -------
-    numpy.ndarray of type numpy.uint16
-        The matrix with the correct 16-bit bitmaps for each cell.
-    """
-
-    def generator(width: int, height: int, num_agents: int, num_resets: int = 0,
-                  np_random: RandomState = None) -> RailGenerator:
-
-        if num_agents > nr_start_goal:
-            num_agents = nr_start_goal
-            print("complex_rail_generator: num_agents > nr_start_goal, changing num_agents")
-        grid_map = GridTransitionMap(width=width, height=height, transitions=RailEnvTransitions())
-        rail_array = grid_map.grid
-        rail_array.fill(0)
-
-        # generate rail array
-        # step 1:
-        # - generate a start and goal position
-        #   - validate min/max distance allowed
-        #   - validate that start/goals are not placed too close to other start/goals
-        #   - draw a rail from [start,goal]
-        #     - if rail crosses existing rail then validate new connection
-        #     - possibility that this fails to create a path to goal
-        #     - on failure generate new start/goal
-        #
-        # step 2:
-        # - add more rails to map randomly between cells that have rails
-        #   - validate all new rails, on failure don't add new rails
-        #
-        # step 3:
-        # - return transition map + list of [start_pos, start_dir, goal_pos] points
-        #
-
-        rail_trans = grid_map.transitions
-        start_goal = []
-        start_dir = []
-        nr_created = 0
-        created_sanity = 0
-        sanity_max = 9000
-        while nr_created < nr_start_goal and created_sanity < sanity_max:
-            all_ok = False
-            for _ in range(sanity_max):
-                start = (np_random.randint(0, height), np_random.randint(0, width))
-                goal = (np_random.randint(0, height), np_random.randint(0, width))
-
-                # check to make sure start,goal pos is empty?
-                if rail_array[goal] != 0 or rail_array[start] != 0:
-                    continue
-                # check min/max distance
-                dist_sg = distance_on_rail(start, goal)
-                if dist_sg < min_dist:
-                    continue
-                if dist_sg > max_dist:
-                    continue
-                # check distance to existing points
-                sg_new = [start, goal]
-
-                def check_all_dist(sg_new):
-                    """
-                    Function to check the distance betweens start and goal
-                    :param sg_new: start and goal tuple
-                    :return: True if distance is larger than 2, False otherwise
-                    """
-                    for sg in start_goal:
-                        for i in range(2):
-                            for j in range(2):
-                                dist = distance_on_rail(sg_new[i], sg[j])
-                                if dist < 2:
-                                    return False
-                    return True
-
-                if check_all_dist(sg_new):
-                    all_ok = True
-                    break
-
-            if not all_ok:
-                # we might as well give up at this point
-                break
-
-            new_path = connect_rail_in_grid_map(grid_map, start, goal, rail_trans, Vec2d.get_chebyshev_distance,
-                                                flip_start_node_trans=True, flip_end_node_trans=True,
-                                                respect_transition_validity=True, forbidden_cells=None)
-            if len(new_path) >= 2:
-                nr_created += 1
-                start_goal.append([start, goal])
-                start_dir.append(mirror(get_direction(new_path[0], new_path[1])))
-            else:
-                # after too many failures we will give up
-                created_sanity += 1
-
-        # add extra connections between existing rail
-        created_sanity = 0
-        nr_created = 0
-        while nr_created < nr_extra and created_sanity < sanity_max:
-            all_ok = False
-            for _ in range(sanity_max):
-                start = (np_random.randint(0, height), np_random.randint(0, width))
-                goal = (np_random.randint(0, height), np_random.randint(0, width))
-                # check to make sure start,goal pos are not empty
-                if rail_array[goal] == 0 or rail_array[start] == 0:
-                    continue
-                else:
-                    all_ok = True
-                    break
-            if not all_ok:
-                break
-            new_path = connect_rail_in_grid_map(grid_map, start, goal, rail_trans, Vec2d.get_chebyshev_distance,
-                                                flip_start_node_trans=True, flip_end_node_trans=True,
-                                                respect_transition_validity=True, forbidden_cells=None)
-
-            if len(new_path) >= 2:
-                nr_created += 1
-            else:
-                # after too many failures we will give up
-                created_sanity += 1
-
-        return grid_map, {'agents_hints': {
-            'start_goal': start_goal,
-            'start_dir': start_dir
-        }}
-
-    return generator
-
-
 def rail_from_manual_specifications_generator(rail_spec):
     """
     Utility to convert a rail given by manual specification as a map of tuples
@@ -285,321 +144,17 @@ def rail_from_file(filename, load_from_package=None) -> RailGenerator:
     return generator
 
 class RailFromGridGen(RailGen):
-    def __init__(self, rail_map):
+    def __init__(self, rail_map, optionals=None):
         self.rail_map = rail_map
+        self.optionals = optionals
 
     def generate(self, width: int, height: int, num_agents: int, num_resets: int = 0,
                   np_random: RandomState = None) -> RailGenerator:
-        return self.rail_map, None
-
-
-def rail_from_grid_transition_map(rail_map) -> RailGenerator:
-    return RailFromGridGen(rail_map)
-
-def rail_from_grid_transition_map_old(rail_map) -> RailGenerator:
-    """
-    Utility to convert a rail given by a GridTransitionMap map with the correct
-    16-bit transitions specifications.
-
-    Parameters
-    ----------
-    rail_map : GridTransitionMap object
-        GridTransitionMap object to return when the generator is called.
-
-    Returns
-    -------
-    function
-        Generator function that always returns the given `rail_map` object.
-    """
-
-    def generator(width: int, height: int, num_agents: int, num_resets: int = 0,
-                  np_random: RandomState = None) -> RailGenerator:
-        return rail_map, None
-
-    return generator
-
-
-def random_rail_generator(cell_type_relative_proportion=[1.0] * 11, seed=1) -> RailGenerator:
-    """
-    Dummy random level generator:
-    - fill in cells at random in [width-2, height-2]
-    - keep filling cells in among the unfilled ones, such that all transitions\
-      are legit;  if no cell can be filled in without violating some\
-      transitions, pick one among those that can satisfy most transitions\
-      (1,2,3 or 4), and delete (+mark to be re-filled) the cells that were\
-      incompatible.
-    - keep trying for a total number of insertions\
-      (e.g., (W-2)*(H-2)*MAX_REPETITIONS ); if no solution is found, empty the\
-      board and try again from scratch.
-    - finally pad the border of the map with dead-ends to avoid border issues.
-
-    Dead-ends are not allowed inside the grid, only at the border; however, if
-    no cell type can be inserted in a given cell (because of the neighboring
-    transitions), deadends are allowed if they solve the problem. This was
-    found to turn most un-genereatable levels into valid ones.
-
-    Parameters
-    ----------
-    width : int
-        The width (number of cells) of the grid to generate.
-    height : int
-        The height (number of cells) of the grid to generate.
-
-    Returns
-    -------
-    numpy.ndarray of type numpy.uint16
-        The matrix with the correct 16-bit bitmaps for each cell.
-    """
-
-    def generator(width: int, height: int, num_agents: int, num_resets: int = 0,
-                  np_random: RandomState = None) -> RailGenerator:
-        t_utils = RailEnvTransitions()
-
-        transition_probability = cell_type_relative_proportion
-
-        transitions_templates_ = []
-        transition_probabilities = []
-        for i in range(len(t_utils.transitions)):  # don't include dead-ends
-            if t_utils.transitions[i] == int('0010000000000000', 2):
-                continue
-
-            all_transitions = 0
-            for dir_ in range(4):
-                trans = t_utils.get_transitions(t_utils.transitions[i], dir_)
-                all_transitions |= (trans[0] << 3) | \
-                                   (trans[1] << 2) | \
-                                   (trans[2] << 1) | \
-                                   (trans[3])
-
-            template = [int(x) for x in bin(all_transitions)[2:]]
-            template = [0] * (4 - len(template)) + template
-
-            # add all rotations
-            for rot in [0, 90, 180, 270]:
-                transitions_templates_.append((template,
-                                               t_utils.rotate_transition(
-                                                   t_utils.transitions[i],
-                                                   rot)))
-                transition_probabilities.append(transition_probability[i])
-                template = [template[-1]] + template[:-1]
-
-        def get_matching_templates(template):
-            """
-            Returns a list of possible transition maps for a given template
-
-            Parameters:
-            ------
-            template:List[int]
-
-            Returns:
-            ------
-            List[int]
-            """
-            ret = []
-            for i in range(len(transitions_templates_)):
-                is_match = True
-                for j in range(4):
-                    if template[j] >= 0 and template[j] != transitions_templates_[i][0][j]:
-                        is_match = False
-                        break
-                if is_match:
-                    ret.append((transitions_templates_[i][1], transition_probabilities[i]))
-            return ret
-
-        MAX_INSERTIONS = (width - 2) * (height - 2) * 10
-        MAX_ATTEMPTS_FROM_SCRATCH = 10
-
-        attempt_number = 0
-        while attempt_number < MAX_ATTEMPTS_FROM_SCRATCH:
-            cells_to_fill = []
-            rail = []
-            for r in range(height):
-                rail.append([None] * width)
-                if r > 0 and r < height - 1:
-                    cells_to_fill = cells_to_fill + [(r, c) for c in range(1, width - 1)]
-
-            num_insertions = 0
-            while num_insertions < MAX_INSERTIONS and len(cells_to_fill) > 0:
-                cell = cells_to_fill[np_random.choice(len(cells_to_fill), 1)[0]]
-                cells_to_fill.remove(cell)
-                row = cell[0]
-                col = cell[1]
-
-                # look at its neighbors and see what are the possible transitions
-                # that can be chosen from, if any.
-                valid_template = [-1, -1, -1, -1]
-
-                for el in [(0, 2, (-1, 0)),
-                           (1, 3, (0, 1)),
-                           (2, 0, (1, 0)),
-                           (3, 1, (0, -1))]:  # N, E, S, W
-                    neigh_trans = rail[row + el[2][0]][col + el[2][1]]
-                    if neigh_trans is not None:
-                        # select transition coming from facing direction el[1] and
-                        # moving to direction el[1]
-                        max_bit = 0
-                        for k in range(4):
-                            max_bit |= t_utils.get_transition(neigh_trans, k, el[1])
-
-                        if max_bit:
-                            valid_template[el[0]] = 1
-                        else:
-                            valid_template[el[0]] = 0
-
-                possible_cell_transitions = get_matching_templates(valid_template)
-
-                if len(possible_cell_transitions) == 0:  # NO VALID TRANSITIONS
-                    # no cell can be filled in without violating some transitions
-                    # can a dead-end solve the problem?
-                    if valid_template.count(1) == 1:
-                        for k in range(4):
-                            if valid_template[k] == 1:
-                                rot = 0
-                                if k == 0:
-                                    rot = 180
-                                elif k == 1:
-                                    rot = 270
-                                elif k == 2:
-                                    rot = 0
-                                elif k == 3:
-                                    rot = 90
-
-                                rail[row][col] = t_utils.rotate_transition(int('0010000000000000', 2), rot)
-                                num_insertions += 1
-
-                                break
-
-                    else:
-                        # can I get valid transitions by removing a single
-                        # neighboring cell?
-                        bestk = -1
-                        besttrans = []
-                        for k in range(4):
-                            tmp_template = valid_template[:]
-                            tmp_template[k] = -1
-                            possible_cell_transitions = get_matching_templates(tmp_template)
-                            if len(possible_cell_transitions) > len(besttrans):
-                                besttrans = possible_cell_transitions
-                                bestk = k
-
-                        if bestk >= 0:
-                            # Replace the corresponding cell with None, append it
-                            # to cells to fill, fill in a transition in the current
-                            # cell.
-                            replace_row = row - 1
-                            replace_col = col
-                            if bestk == 1:
-                                replace_row = row
-                                replace_col = col + 1
-                            elif bestk == 2:
-                                replace_row = row + 1
-                                replace_col = col
-                            elif bestk == 3:
-                                replace_row = row
-                                replace_col = col - 1
-
-                            cells_to_fill.append((replace_row, replace_col))
-                            rail[replace_row][replace_col] = None
-
-                            possible_transitions, possible_probabilities = zip(*besttrans)
-                            possible_probabilities = [p / sum(possible_probabilities) for p in possible_probabilities]
-
-                            rail[row][col] = np_random.choice(possible_transitions,
-                                                              p=possible_probabilities)
-                            num_insertions += 1
-
-                        else:
-                            print('WARNING: still nothing!')
-                            rail[row][col] = int('0000000000000000', 2)
-                            num_insertions += 1
-                            pass
-
-                else:
-                    possible_transitions, possible_probabilities = zip(*possible_cell_transitions)
-                    possible_probabilities = [p / sum(possible_probabilities) for p in possible_probabilities]
-
-                    rail[row][col] = np_random.choice(possible_transitions,
-                                                      p=possible_probabilities)
-                    num_insertions += 1
-
-            if num_insertions == MAX_INSERTIONS:
-                # Failed to generate a valid level; try again for a number of times
-                attempt_number += 1
-            else:
-                break
-
-        if attempt_number == MAX_ATTEMPTS_FROM_SCRATCH:
-            print('ERROR: failed to generate level')
-
-        # Finally pad the border of the map with dead-ends to avoid border issues;
-        # at most 1 transition in the neigh cell
-        for r in range(height):
-            # Check for transitions coming from [r][1] to WEST
-            max_bit = 0
-            neigh_trans = rail[r][1]
-            if neigh_trans is not None:
-                for k in range(4):
-                    neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2 ** 4 - 1)
-                    max_bit = max_bit | (neigh_trans_from_direction & 1)
-            if max_bit:
-                rail[r][0] = t_utils.rotate_transition(int('0010000000000000', 2), 270)
-            else:
-                rail[r][0] = int('0000000000000000', 2)
-
-            # Check for transitions coming from [r][-2] to EAST
-            max_bit = 0
-            neigh_trans = rail[r][-2]
-            if neigh_trans is not None:
-                for k in range(4):
-                    neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2 ** 4 - 1)
-                    max_bit = max_bit | (neigh_trans_from_direction & (1 << 2))
-            if max_bit:
-                rail[r][-1] = t_utils.rotate_transition(int('0010000000000000', 2),
-                                                        90)
-            else:
-                rail[r][-1] = int('0000000000000000', 2)
-
-        for c in range(width):
-            # Check for transitions coming from [1][c] to NORTH
-            max_bit = 0
-            neigh_trans = rail[1][c]
-            if neigh_trans is not None:
-                for k in range(4):
-                    neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2 ** 4 - 1)
-                    max_bit = max_bit | (neigh_trans_from_direction & (1 << 3))
-            if max_bit:
-                rail[0][c] = int('0010000000000000', 2)
-            else:
-                rail[0][c] = int('0000000000000000', 2)
-
-            # Check for transitions coming from [-2][c] to SOUTH
-            max_bit = 0
-            neigh_trans = rail[-2][c]
-            if neigh_trans is not None:
-                for k in range(4):
-                    neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2 ** 4 - 1)
-                    max_bit = max_bit | (neigh_trans_from_direction & (1 << 1))
-            if max_bit:
-                rail[-1][c] = t_utils.rotate_transition(int('0010000000000000', 2), 180)
-            else:
-                rail[-1][c] = int('0000000000000000', 2)
-
-        # For display only, wrong levels
-        for r in range(height):
-            for c in range(width):
-                if rail[r][c] is None:
-                    rail[r][c] = int('0000000000000000', 2)
-
-        tmp_rail = np.asarray(rail, dtype=np.uint16)
-
-        return_rail = GridTransitionMap(width=width, height=height, transitions=t_utils)
-        return_rail.grid = tmp_rail
-
-        return return_rail, None
-
-    return generator
+        return self.rail_map, self.optionals
 
 
+def rail_from_grid_transition_map(rail_map, optionals=None) -> RailGenerator:
+    return RailFromGridGen(rail_map, optionals)
 
 
 def sparse_rail_generator(*args, **kwargs):
@@ -608,7 +163,7 @@ def sparse_rail_generator(*args, **kwargs):
 class SparseRailGen(RailGen):
 
     def __init__(self, max_num_cities: int = 5, grid_mode: bool = False, max_rails_between_cities: int = 4,
-                          max_rails_in_city: int = 4, seed=0) -> RailGenerator:
+                          max_rail_pairs_in_city: int = 2, seed=0) -> RailGenerator:
         """
         Generates railway networks with cities and inner city rails
 
@@ -621,7 +176,7 @@ class SparseRailGen(RailGen):
         max_rails_between_cities: int
             Max number of rails connecting to a city. This is only the number of connection points at city boarder.
             Number of tracks drawn inbetween cities can still vary
-        max_rails_in_city: int
+        max_rail_pairs_in_city: int
             Number of parallel tracks in the city. This represents the number of tracks in the trainstations
         seed: int
             Initiate the seed
@@ -633,7 +188,7 @@ class SparseRailGen(RailGen):
         self.max_num_cities = max_num_cities
         self.grid_mode = grid_mode
         self.max_rails_between_cities = max_rails_between_cities
-        self.max_rails_in_city = max_rails_in_city
+        self.max_rail_pairs_in_city = max_rail_pairs_in_city
         self.seed = seed # TODO: seed in constructor or generate?
 
 
@@ -667,18 +222,20 @@ class SparseRailGen(RailGen):
             
         rail_trans = RailEnvTransitions()
         grid_map = GridTransitionMap(width=width, height=height, transitions=rail_trans)
+
+        # NEW : SCHED CONST (Pairs of rails (1,2,3 pairs))
+        min_nr_rail_pairs_in_city = 1 # (min pair must be 1)
+        rail_pairs_in_city = min_nr_rail_pairs_in_city if self.max_rail_pairs_in_city < min_nr_rail_pairs_in_city else self.max_rail_pairs_in_city # (pairs can be 1,2,3)
+        rails_between_cities = (rail_pairs_in_city*2) if self.max_rails_between_cities > (rail_pairs_in_city*2) else self.max_rails_between_cities
+
         # We compute the city radius by the given max number of rails it can contain.
         # The radius is equal to the number of tracks divided by 2
         # We add 2 cells to avoid that track lenght is to short
         city_padding = 2
         # We use ceil if we get uneven numbers of city radius. This is to guarantee that all rails fit within the city.
-        city_radius = int(np.ceil((self.max_rails_in_city) / 2)) + city_padding
+        city_radius = int(np.ceil((rail_pairs_in_city*2) / 2)) + city_padding
         vector_field = np.zeros(shape=(height, width)) - 1.
 
-        min_nr_rails_in_city = 2
-        rails_in_city = min_nr_rails_in_city if self.max_rails_in_city < min_nr_rails_in_city else self.max_rails_in_city
-        rails_between_cities = rails_in_city if self.max_rails_between_cities > rails_in_city else self.max_rails_between_cities
-
         # Calculate the max number of cities allowed
         # and reduce the number of cities to build to avoid problems
         max_feasible_cities = min(self.max_num_cities,
@@ -709,7 +266,7 @@ class SparseRailGen(RailGen):
         inner_connection_points, outer_connection_points, city_orientations, city_cells = \
             self._generate_city_connection_points(
                 city_positions, city_radius, vector_field, rails_between_cities,
-                rails_in_city, np_random=np_random)
+                rail_pairs_in_city, np_random=np_random)
 
         # Connect the cities through the connection points
         inter_city_lines = self._connect_cities(city_positions, outer_connection_points, city_cells,
@@ -726,7 +283,6 @@ class SparseRailGen(RailGen):
 
         # Fix all transition elements
         self._fix_transitions(city_cells, inter_city_lines, grid_map, vector_field)
-
         return grid_map, {'agents_hints': {
             'num_agents': num_agents,
             'city_positions': city_positions,
@@ -831,7 +387,7 @@ class SparseRailGen(RailGen):
 
     def _generate_city_connection_points(self, city_positions: IntVector2DArray, city_radius: int,
                                          vector_field: IntVector2DArray, rails_between_cities: int,
-                                         rails_in_city: int = 2, np_random: RandomState = None) -> (
+                                         rail_pairs_in_city: int = 1, np_random: RandomState = None) -> (
         List[List[List[IntVector2D]]],
         List[List[List[IntVector2D]]],
         List[np.ndarray],
@@ -852,7 +408,7 @@ class SparseRailGen(RailGen):
             Each cell contains the prefered orientation of cells. If no prefered orientation is present it is set to -1
         rails_between_cities: int
             Number of rails that connect out from the city
-        rails_in_city: int
+        rail_pairs_in_city: int
             Number of rails within the city
 
         Returns
@@ -895,7 +451,8 @@ class SparseRailGen(RailGen):
             city_cells.extend(self._get_cells_in_city(city_position, city_radius, city_orientations[-1], vector_field))
             # set the number of tracks within a city, at least 2 tracks per city
             connections_per_direction = np.zeros(4, dtype=int)
-            nr_of_connection_points = np_random.randint(2, rails_in_city + 1)
+            # NEW : SCHED CONST
+            nr_of_connection_points = np_random.randint(1, rail_pairs_in_city + 1) * 2  # can be (1,2,3)*2 = (2,4,6)
             for idx in connection_sides_idx:
                 connections_per_direction[idx] = nr_of_connection_points
             connection_points_coordinates_inner: List[List[IntVector2D]] = [[] for i in range(4)]
diff --git a/flatland/envs/schedule_generators.py b/flatland/envs/schedule_generators.py
index c1789435fbca195f2a02035e481fbbf95a826407..6abaddd0098a2bff27fff06de2cbcacda8a05ac6 100644
--- a/flatland/envs/schedule_generators.py
+++ b/flatland/envs/schedule_generators.py
@@ -1,351 +1 @@
-"""Schedule generators (railway undertaking, "EVU")."""
-import warnings
-from typing import Tuple, List, Callable, Mapping, Optional, Any
-
-import numpy as np
-from numpy.random.mtrand import RandomState
-
-from flatland.core.grid.grid4_utils import get_new_position
-from flatland.core.transition_map import GridTransitionMap
-from flatland.envs.agent_utils import EnvAgent
-from flatland.envs.schedule_utils import Schedule
-from flatland.envs import persistence
-
-AgentPosition = Tuple[int, int]
-ScheduleGenerator = Callable[[GridTransitionMap, int, Optional[Any], Optional[int]], Schedule]
-
-
-def speed_initialization_helper(nb_agents: int, speed_ratio_map: Mapping[float, float] = None,
-                                seed: int = None, np_random: RandomState = None) -> List[float]:
-    """
-    Parameters
-    ----------
-    nb_agents : int
-        The number of agents to generate a speed for
-    speed_ratio_map : Mapping[float,float]
-        A map of speeds mappint to their ratio of appearance. The ratios must sum up to 1.
-
-    Returns
-    -------
-    List[float]
-        A list of size nb_agents of speeds with the corresponding probabilistic ratios.
-    """
-    if speed_ratio_map is None:
-        return [1.0] * nb_agents
-
-    nb_classes = len(speed_ratio_map.keys())
-    speed_ratio_map_as_list: List[Tuple[float, float]] = list(speed_ratio_map.items())
-    speed_ratios = list(map(lambda t: t[1], speed_ratio_map_as_list))
-    speeds = list(map(lambda t: t[0], speed_ratio_map_as_list))
-    return list(map(lambda index: speeds[index], np_random.choice(nb_classes, nb_agents, p=speed_ratios)))
-
-
-class BaseSchedGen(object):
-    def __init__(self, speed_ratio_map: Mapping[float, float] = None, seed: int = 1):
-        self.speed_ratio_map = speed_ratio_map
-        self.seed = seed
-
-    def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any=None, num_resets: int = 0,
-        np_random: RandomState = None) -> Schedule:
-        pass
-
-    def __call__(self, *args, **kwargs):
-        return self.generate(*args, **kwargs)
-
-
-
-def complex_schedule_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> ScheduleGenerator:
-    """
-
-    Generator used to generate the levels of Round 1 in the Flatland Challenge. It can only be used together
-    with complex_rail_generator. It places agents at end and start points provided by the rail generator.
-    It assigns speeds to the different agents according to the speed_ratio_map
-    :param speed_ratio_map: Speed ratios of all agents. They are probabilities of all different speeds and have to
-            add up to 1.
-    :param seed: Initiate random seed generator
-    :return:
-    """
-
-    def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0,
-                  np_random: RandomState = None) -> Schedule:
-        """
-
-        The generator that assigns tasks to all the agents
-        :param rail: Rail infrastructure given by the rail_generator
-        :param num_agents: Number of agents to include in the schedule
-        :param hints: Hints provided by the rail_generator These include positions of start/target positions
-        :param num_resets: How often the generator has been reset.
-        :return: Returns the generator to the rail constructor
-        """
-        # Todo: Remove parameters and variables not used for next version, Issue: <https://gitlab.aicrowd.com/flatland/flatland/issues/305>
-        _runtime_seed = seed + num_resets
-
-        start_goal = hints['start_goal']
-        start_dir = hints['start_dir']
-        agents_position = [sg[0] for sg in start_goal[:num_agents]]
-        agents_target = [sg[1] for sg in start_goal[:num_agents]]
-        agents_direction = start_dir[:num_agents]
-
-        if speed_ratio_map:
-            speeds = speed_initialization_helper(num_agents, speed_ratio_map, seed=_runtime_seed, np_random=np_random)
-        else:
-            speeds = [1.0] * len(agents_position)
-        # Compute max number of steps with given schedule
-        extra_time_factor = 1.5  # Factor to allow for more then minimal time
-        max_episode_steps = int(extra_time_factor * rail.height * rail.width)
-
-        return Schedule(agent_positions=agents_position, agent_directions=agents_direction,
-                        agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None,
-                        max_episode_steps=max_episode_steps)
-
-    return generator
-
-
-def sparse_schedule_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> ScheduleGenerator:
-    return SparseSchedGen(speed_ratio_map, seed)
-
-
-class SparseSchedGen(BaseSchedGen):
-    """
-
-    This is the schedule generator which is used for Round 2 of the Flatland challenge. It produces schedules
-    to railway networks provided by sparse_rail_generator.
-    :param speed_ratio_map: Speed ratios of all agents. They are probabilities of all different speeds and have to
-            add up to 1.
-    :param seed: Initiate random seed generator
-    """
-
-    def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0,
-                  np_random: RandomState = None) -> Schedule:
-        """
-
-        The generator that assigns tasks to all the agents
-        :param rail: Rail infrastructure given by the rail_generator
-        :param num_agents: Number of agents to include in the schedule
-        :param hints: Hints provided by the rail_generator These include positions of start/target positions
-        :param num_resets: How often the generator has been reset.
-        :return: Returns the generator to the rail constructor
-        """
-
-        _runtime_seed = self.seed + num_resets
-
-        train_stations = hints['train_stations']
-        city_positions = hints['city_positions']
-        city_orientation = hints['city_orientations']
-        max_num_agents = hints['num_agents']
-        city_orientations = hints['city_orientations']
-        if num_agents > max_num_agents:
-            num_agents = max_num_agents
-            warnings.warn("Too many agents! Changes number of agents.")
-        # Place agents and targets within available train stations
-        agents_position = []
-        agents_target = []
-        agents_direction = []
-
-        for agent_idx in range(num_agents):
-            infeasible_agent = True
-            tries = 0
-            while infeasible_agent:
-                tries += 1
-                infeasible_agent = False
-                # Set target for agent
-                city_idx = np_random.choice(len(city_positions), 2, replace=False)
-                start_city = city_idx[0]
-                target_city = city_idx[1]
-
-                start_idx = np_random.choice(np.arange(len(train_stations[start_city])))
-                target_idx = np_random.choice(np.arange(len(train_stations[target_city])))
-                start = train_stations[start_city][start_idx]
-                target = train_stations[target_city][target_idx]
-
-                while start[1] % 2 != 0:
-                    start_idx = np_random.choice(np.arange(len(train_stations[start_city])))
-                    start = train_stations[start_city][start_idx]
-                while target[1] % 2 != 1:
-                    target_idx = np_random.choice(np.arange(len(train_stations[target_city])))
-                    target = train_stations[target_city][target_idx]
-                possible_orientations = [city_orientation[start_city],
-                                         (city_orientation[start_city] + 2) % 4]
-                agent_orientation = np_random.choice(possible_orientations)
-                if not rail.check_path_exists(start[0], agent_orientation, target[0]):
-                    agent_orientation = (agent_orientation + 2) % 4
-                if not (rail.check_path_exists(start[0], agent_orientation, target[0])):
-                    infeasible_agent = True
-                if tries >= 100:
-                    warnings.warn("Did not find any possible path, check your parameters!!!")
-                    break
-            agents_position.append((start[0][0], start[0][1]))
-            agents_target.append((target[0][0], target[0][1]))
-
-            agents_direction.append(agent_orientation)
-            # Orient the agent correctly
-
-        if self.speed_ratio_map:
-            speeds = speed_initialization_helper(num_agents, self.speed_ratio_map, seed=_runtime_seed, np_random=np_random)
-        else:
-            speeds = [1.0] * len(agents_position)
-
-        # We add multiply factors to the max number of time steps to simplify task in Flatland challenge.
-        # These factors might change in the future.
-        timedelay_factor = 4
-        alpha = 2
-        max_episode_steps = int(
-            timedelay_factor * alpha * (rail.width + rail.height + num_agents / len(city_positions)))
-
-        return Schedule(agent_positions=agents_position, agent_directions=agents_direction,
-                        agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None,
-                        max_episode_steps=max_episode_steps)
-
-
-def random_schedule_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> ScheduleGenerator:
-    return RandomSchedGen(speed_ratio_map, seed)
-
-
-class RandomSchedGen(BaseSchedGen):
-    
-    """
-    Given a `rail` GridTransitionMap, return a random placement of agents (initial position, direction and target).
-
-    Parameters
-    ----------
-        speed_ratio_map : Optional[Mapping[float, float]]
-            A map of speeds mapping to their ratio of appearance. The ratios must sum up to 1.
-
-    Returns
-    -------
-        Tuple[List[Tuple[int,int]], List[Tuple[int,int]], List[Tuple[int,int]], List[float]]
-            initial positions, directions, targets speeds
-    """
-
-    def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0,
-                  np_random: RandomState = None) -> Schedule:
-        _runtime_seed = self.seed + num_resets
-
-        valid_positions = []
-        for r in range(rail.height):
-            for c in range(rail.width):
-                if rail.get_full_transitions(r, c) > 0:
-                    valid_positions.append((r, c))
-        if len(valid_positions) == 0:
-            return Schedule(agent_positions=[], agent_directions=[],
-                            agent_targets=[], agent_speeds=[], agent_malfunction_rates=None, max_episode_steps=0)
-
-        if len(valid_positions) < num_agents:
-            warnings.warn("schedule_generators: len(valid_positions) < num_agents")
-            return Schedule(agent_positions=[], agent_directions=[],
-                            agent_targets=[], agent_speeds=[], agent_malfunction_rates=None, max_episode_steps=0)
-
-        agents_position_idx = [i for i in np_random.choice(len(valid_positions), num_agents, replace=False)]
-        agents_position = [valid_positions[agents_position_idx[i]] for i in range(num_agents)]
-        agents_target_idx = [i for i in np_random.choice(len(valid_positions), num_agents, replace=False)]
-        agents_target = [valid_positions[agents_target_idx[i]] for i in range(num_agents)]
-        update_agents = np.zeros(num_agents)
-
-        re_generate = True
-        cnt = 0
-        while re_generate:
-            cnt += 1
-            if cnt > 1:
-                print("re_generate cnt={}".format(cnt))
-            if cnt > 1000:
-                raise Exception("After 1000 re_generates still not success, giving up.")
-            # update position
-            for i in range(num_agents):
-                if update_agents[i] == 1:
-                    x = np.setdiff1d(np.arange(len(valid_positions)), agents_position_idx)
-                    agents_position_idx[i] = np_random.choice(x)
-                    agents_position[i] = valid_positions[agents_position_idx[i]]
-                    x = np.setdiff1d(np.arange(len(valid_positions)), agents_target_idx)
-                    agents_target_idx[i] = np_random.choice(x)
-                    agents_target[i] = valid_positions[agents_target_idx[i]]
-            update_agents = np.zeros(num_agents)
-
-            # agents_direction must be a direction for which a solution is
-            # guaranteed.
-            agents_direction = [0] * num_agents
-            re_generate = False
-            for i in range(num_agents):
-                valid_movements = []
-                for direction in range(4):
-                    position = agents_position[i]
-                    moves = rail.get_transitions(position[0], position[1], direction)
-                    for move_index in range(4):
-                        if moves[move_index]:
-                            valid_movements.append((direction, move_index))
-
-                valid_starting_directions = []
-                for m in valid_movements:
-                    new_position = get_new_position(agents_position[i], m[1])
-                    if m[0] not in valid_starting_directions and rail.check_path_exists(new_position, m[1],
-                                                                                        agents_target[i]):
-                        valid_starting_directions.append(m[0])
-
-                if len(valid_starting_directions) == 0:
-                    update_agents[i] = 1
-                    warnings.warn(
-                        "reset position for agent[{}]: {} -> {}".format(i, agents_position[i], agents_target[i]))
-                    re_generate = True
-                    break
-                else:
-                    agents_direction[i] = valid_starting_directions[
-                        np_random.choice(len(valid_starting_directions), 1)[0]]
-
-        agents_speed = speed_initialization_helper(num_agents, self.speed_ratio_map, seed=_runtime_seed, 
-            np_random=np_random)
-
-        # Compute max number of steps with given schedule
-        extra_time_factor = 1.5  # Factor to allow for more then minimal time
-        max_episode_steps = int(extra_time_factor * rail.height * rail.width)
-
-        return Schedule(agent_positions=agents_position, agent_directions=agents_direction,
-                        agent_targets=agents_target, agent_speeds=agents_speed, agent_malfunction_rates=None,
-                        max_episode_steps=max_episode_steps)
-
-
-
-def schedule_from_file(filename, load_from_package=None) -> ScheduleGenerator:
-    """
-    Utility to load pickle file
-
-    Parameters
-    ----------
-    input_file : Pickle file generated by env.save() or editor
-
-    Returns
-    -------
-    Tuple[List[Tuple[int,int]], List[Tuple[int,int]], List[Tuple[int,int]], List[float]]
-        initial positions, directions, targets speeds
-    """
-
-    def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0,
-                  np_random: RandomState = None) -> Schedule:
-
-        env_dict = persistence.RailEnvPersister.load_env_dict(filename, load_from_package=load_from_package)
-
-        max_episode_steps = env_dict.get("max_episode_steps", 0)
-        if (max_episode_steps==0):
-            print("This env file has no max_episode_steps (deprecated) - setting to 100")
-            max_episode_steps = 100
-            
-        agents = env_dict["agents"]
-
-        #print("schedule generator from_file - agents: ", agents)
-
-        # setup with loaded data
-        agents_position = [a.initial_position for a in agents]
-
-        # this logic is wrong - we should really load the initial_direction as the direction.
-        #agents_direction = [a.direction for a in agents]
-        agents_direction = [a.initial_direction for a in agents]
-        agents_target = [a.target for a in agents]
-        agents_speed = [a.speed_data['speed'] for a in agents]
-
-        # Malfunctions from here are not used.  They have their own generator.
-        #agents_malfunction = [a.malfunction_data['malfunction_rate'] for a in agents]
-
-        return Schedule(agent_positions=agents_position, agent_directions=agents_direction,
-                        agent_targets=agents_target, agent_speeds=agents_speed, 
-                        agent_malfunction_rates=None,
-                        max_episode_steps=max_episode_steps)
-
-    return generator
+raise ImportError(" Schedule Generators is now renamed to line_generators, any reference to schedule should be replaced with line")
\ No newline at end of file
diff --git a/flatland/envs/schedule_utils.py b/flatland/envs/schedule_utils.py
deleted file mode 100644
index a811ea4af7d7f7faccfe16e94adf117cba05d6b8..0000000000000000000000000000000000000000
--- a/flatland/envs/schedule_utils.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import List, NamedTuple
-
-from flatland.core.grid.grid4 import Grid4TransitionsEnum
-from flatland.core.grid.grid_utils import IntVector2DArray
-
-Schedule = NamedTuple('Schedule', [('agent_positions', IntVector2DArray),
-                                   ('agent_directions', List[Grid4TransitionsEnum]),
-                                   ('agent_targets', IntVector2DArray),
-                                   ('agent_speeds', List[float]),
-                                   ('agent_malfunction_rates', List[int]),
-                                   ('max_episode_steps', int)])
diff --git a/flatland/envs/timetable_generators.py b/flatland/envs/timetable_generators.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7876d742f61db830883f828faaf99a39a48bc65
--- /dev/null
+++ b/flatland/envs/timetable_generators.py
@@ -0,0 +1,96 @@
+import os
+import json
+import itertools
+import warnings
+from typing import Tuple, List, Callable, Mapping, Optional, Any
+from flatland.envs.timetable_utils import Timetable
+
+import numpy as np
+from numpy.random.mtrand import RandomState
+
+from flatland.envs.agent_utils import EnvAgent
+from flatland.envs.distance_map import DistanceMap
+from flatland.envs.rail_env_shortest_paths import get_shortest_paths
+
+def len_handle_none(v):
+    if v is not None:
+        return len(v)
+    else:
+        return 0
+
+def timetable_generator(agents: List[EnvAgent], distance_map: DistanceMap, 
+                            agents_hints: dict, np_random: RandomState = None) -> Timetable:
+    """
+    Calculates earliest departure and latest arrival times for the agents
+    This is the new addition in Flatland 3
+    Also calculates the max episodes steps based on the density of the timetable
+
+    inputs: 
+        agents - List of all the agents rail_env.agents
+        distance_map - Distance map of positions to tagets of each agent in each direction
+        agent_hints - Uses the number of cities
+        np_random - RNG state for seeding
+    returns:
+        Timetable with the latest_arrivals, earliest_departures and max_episdode_steps
+    """
+    # max_episode_steps calculation
+    if agents_hints:
+        city_positions = agents_hints['city_positions']
+        num_cities = len(city_positions)
+    else:
+        num_cities = 2
+
+    timedelay_factor = 4
+    alpha = 2
+    max_episode_steps = int(timedelay_factor * alpha * \
+        (distance_map.rail.width + distance_map.rail.height + (len(agents) / num_cities)))
+    
+    # Multipliers
+    old_max_episode_steps_multiplier = 3.0
+    new_max_episode_steps_multiplier = 1.5
+    travel_buffer_multiplier = 1.3 # must be strictly lesser than new_max_episode_steps_multiplier
+    assert new_max_episode_steps_multiplier > travel_buffer_multiplier
+    end_buffer_multiplier = 0.05
+    mean_shortest_path_multiplier = 0.2
+    
+    shortest_paths = get_shortest_paths(distance_map)
+    shortest_paths_lengths = [len_handle_none(v) for k,v in shortest_paths.items()]
+
+    # Find mean_shortest_path_time
+    agent_speeds = [agent.speed_data['speed'] for agent in agents]
+    agent_shortest_path_times = np.array(shortest_paths_lengths)/ np.array(agent_speeds)
+    mean_shortest_path_time = np.mean(agent_shortest_path_times)
+
+    # Deciding on a suitable max_episode_steps
+    longest_speed_normalized_time = np.max(agent_shortest_path_times)
+    mean_path_delay = mean_shortest_path_time * mean_shortest_path_multiplier
+    max_episode_steps_new = int(np.ceil(longest_speed_normalized_time * new_max_episode_steps_multiplier) + mean_path_delay)
+    
+    max_episode_steps_old = int(max_episode_steps * old_max_episode_steps_multiplier)
+
+    max_episode_steps = min(max_episode_steps_new, max_episode_steps_old)
+    
+    end_buffer = int(max_episode_steps * end_buffer_multiplier)
+    latest_arrival_max = max_episode_steps-end_buffer
+
+    # Useless unless needed by returning
+    earliest_departures = []
+    latest_arrivals = []
+
+    for agent in agents:
+        agent_shortest_path_time = agent_shortest_path_times[agent.handle]
+        agent_travel_time_max = int(np.ceil((agent_shortest_path_time * travel_buffer_multiplier) + mean_path_delay))
+        
+        departure_window_max = max(latest_arrival_max - agent_travel_time_max, 1)
+        
+        earliest_departure = np_random.randint(0, departure_window_max)
+        latest_arrival = earliest_departure + agent_travel_time_max
+        
+        earliest_departures.append(earliest_departure)
+        latest_arrivals.append(latest_arrival)
+
+        agent.earliest_departure = earliest_departure
+        agent.latest_arrival = latest_arrival
+
+    return Timetable(earliest_departures=earliest_departures, latest_arrivals=latest_arrivals,
+                    max_episode_steps=max_episode_steps)
diff --git a/flatland/envs/timetable_utils.py b/flatland/envs/timetable_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..548624f2c08879ce0e507224e61b6fe43ffb955b
--- /dev/null
+++ b/flatland/envs/timetable_utils.py
@@ -0,0 +1,14 @@
+from typing import List, NamedTuple
+
+from flatland.core.grid.grid4 import Grid4TransitionsEnum
+from flatland.core.grid.grid_utils import IntVector2DArray
+
+Line = NamedTuple('Line',  [('agent_positions', IntVector2DArray),
+                            ('agent_directions', List[Grid4TransitionsEnum]),
+                            ('agent_targets', IntVector2DArray),
+                            ('agent_speeds', List[float]),
+                            ('agent_malfunction_rates', List[int])])
+
+Timetable = NamedTuple('Timetable',  [('earliest_departures', List[int]),
+                                    ('latest_arrivals', List[int]),
+                                    ('max_episode_steps', int)])
diff --git a/flatland/evaluators/client.py b/flatland/evaluators/client.py
index b31ec52524599cc026bd086acdacf4e69c8c2774..352dd54d118e65e6cec1d0182bbb1959280d6c60 100644
--- a/flatland/evaluators/client.py
+++ b/flatland/evaluators/client.py
@@ -15,7 +15,7 @@ import flatland
 from flatland.envs.malfunction_generators import malfunction_from_file
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_file
-from flatland.envs.schedule_generators import schedule_from_file
+from flatland.envs.line_generators import line_from_file
 from flatland.evaluators import messages
 from flatland.core.env_observation_builder import DummyObservationBuilder
 
@@ -266,7 +266,7 @@ class FlatlandRemoteClient(object):
             print("Current env path : ", test_env_file_path)
         self.current_env_path = test_env_file_path
         self.env = RailEnv(width=1, height=1, rail_generator=rail_from_file(test_env_file_path),
-                           schedule_generator=schedule_from_file(test_env_file_path),
+                           line_generator=line_from_file(test_env_file_path),
                            malfunction_generator_and_process_data=malfunction_from_file(test_env_file_path),
                            obs_builder_object=obs_builder_object)
 
diff --git a/flatland/evaluators/service.py b/flatland/evaluators/service.py
index 03b94380f232773044a2733802e6df4ef9d1918f..92537282d1571ab14d6b009a0ec514d48bc2e6c2 100644
--- a/flatland/evaluators/service.py
+++ b/flatland/evaluators/service.py
@@ -26,7 +26,7 @@ from flatland.envs.agent_utils import RailAgentStatus
 from flatland.envs.malfunction_generators import malfunction_from_file
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_file
-from flatland.envs.schedule_generators import schedule_from_file
+from flatland.envs.line_generators import line_from_file
 from flatland.evaluators import aicrowd_helpers
 from flatland.evaluators import messages
 from flatland.utils.rendertools import RenderTool
@@ -50,7 +50,7 @@ m.patch()
 ########################################################
 
 # Don't proceed to next Test if the previous one didn't reach this mean completion percentage
-TEST_MIN_PERCENTAGE_COMPLETE_MEAN = float(os.getenv("TEST_MIN_PERCENTAGE_COMPLETE_MEAN", 0.25))
+TEST_MIN_PERCENTAGE_COMPLETE_MEAN = float(os.getenv("TEST_MIN_PERCENTAGE_COMPLETE_MEAN", -0.05))
 
 # After this number of consecutive timeouts, kill the submission:
 # this probably means the submission has crashed
@@ -661,6 +661,8 @@ class FlatlandRemoteEvaluationService:
         Handles a ENV_CREATE command from the client
         """
 
+        print(" -- [DEBUG] [env_create] EVAL DONE: ",self.evaluation_done)
+
         # Check if the previous episode was finished
         if not self.simulation_done and not self.evaluation_done:
             _command_response = self._error_template("CAN'T CREATE NEW ENV BEFORE PREVIOUS IS DONE")
@@ -678,6 +680,8 @@ class FlatlandRemoteEvaluationService:
         self.state_env_timed_out = False
 
         # Check if we have finished all the available envs
+        print(" -- [DEBUG] [env_create] SIM COUNT: ", self.simulation_count + 1, len(self.env_file_paths))
+        
         if self.simulation_count >= len(self.env_file_paths):
             self.evaluation_done = True
             # Hack - just ensure these are set
@@ -712,7 +716,7 @@ class FlatlandRemoteEvaluationService:
             """
 
             print("=" * 15)
-            print("Evaluating {} ({}/{})".format(test_env_file_path, self.simulation_count, len(self.env_file_paths)))
+            print("Evaluating {} ({}/{})".format(test_env_file_path, self.simulation_count+1, len(self.env_file_paths)))
 
             test_env_file_path = os.path.join(
                 self.test_env_folder,
@@ -725,6 +729,7 @@ class FlatlandRemoteEvaluationService:
             del self.env
 
             self.env, _env_dict = RailEnvPersister.load_new(test_env_file_path)
+            # distance map here?
 
             self.begin_simulation = time.time()
 
@@ -769,6 +774,7 @@ class FlatlandRemoteEvaluationService:
             _command_response['payload']['info'] = _info
             _command_response['payload']['random_seed'] = RANDOM_SEED
         else:
+            print(" -- [DEBUG] [env_create] return obs = False (END)")
             """
             All test env evaluations are complete
             """
diff --git a/flatland/utils/simple_rail.py b/flatland/utils/simple_rail.py
index 7ac8bb8a0031e4995cda9de14ba093fdefee5e8b..2ee46d02053cdcb179c68d376f3c47c9aab6922a 100644
--- a/flatland/utils/simple_rail.py
+++ b/flatland/utils/simple_rail.py
@@ -42,7 +42,19 @@ def make_simple_rail() -> Tuple[GridTransitionMap, np.array]:
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
-    return rail, rail_map
+    city_positions = [(0,3), (6, 6)]
+    train_stations = [
+                      [( (0, 3), 0 ) ], 
+                      [( (6, 6), 0 ) ],
+                     ]
+    city_orientations = [0, 2]
+    agents_hints = {'num_agents': 2,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+    return rail, rail_map, optionals
 
 
 def make_disconnected_simple_rail() -> Tuple[GridTransitionMap, np.array]:
@@ -82,7 +94,19 @@ def make_disconnected_simple_rail() -> Tuple[GridTransitionMap, np.array]:
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
-    return rail, rail_map
+    city_positions = [(0,3), (6, 6)]
+    train_stations = [
+                      [( (0, 3), 0 ) ], 
+                      [( (6, 6), 0 ) ],
+                     ]
+    city_orientations = [0, 2]
+    agents_hints = {'num_agents': 2,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+    return rail, rail_map, optionals
 
 
 def make_simple_rail2() -> Tuple[GridTransitionMap, np.array]:
@@ -119,7 +143,19 @@ def make_simple_rail2() -> Tuple[GridTransitionMap, np.array]:
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
-    return rail, rail_map
+    city_positions = [(0,3), (6, 6)]
+    train_stations = [
+                      [( (0, 3), 0 ) ], 
+                      [( (6, 6), 0 ) ],
+                     ]
+    city_orientations = [0, 2]
+    agents_hints = {'num_agents': 2,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+    return rail, rail_map, optionals
 
 
 def make_simple_rail_unconnected() -> Tuple[GridTransitionMap, np.array]:
@@ -157,7 +193,19 @@ def make_simple_rail_unconnected() -> Tuple[GridTransitionMap, np.array]:
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
-    return rail, rail_map
+    city_positions = [(0,3), (6, 6)]
+    train_stations = [
+                      [( (0, 3), 0 ) ], 
+                      [( (6, 6), 0 ) ],
+                     ]
+    city_orientations = [0, 2]
+    agents_hints = {'num_agents': 2,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+    return rail, rail_map, optionals
 
 
 def make_simple_rail_with_alternatives() -> Tuple[GridTransitionMap, np.array]:
@@ -201,7 +249,20 @@ def make_simple_rail_with_alternatives() -> Tuple[GridTransitionMap, np.array]:
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
-    return rail, rail_map
+    city_positions = [(0,3), (6, 6)]
+    train_stations = [
+                      [( (0, 3), 0 ) ], 
+                      [( (6, 6), 0 ) ],
+                     ]
+    city_orientations = [0, 2]
+    agents_hints = {'num_agents': 2,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+    return rail, rail_map, optionals
+    
 
 
 def make_invalid_simple_rail() -> Tuple[GridTransitionMap, np.array]:
@@ -239,4 +300,16 @@ def make_invalid_simple_rail() -> Tuple[GridTransitionMap, np.array]:
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
-    return rail, rail_map
+    city_positions = [(0,3), (6, 6)]
+    train_stations = [
+                      [( (0, 3), 0 ) ], 
+                      [( (6, 6), 0 ) ],
+                     ]
+    city_orientations = [0, 2]
+    agents_hints = {'num_agents': 2,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+    return rail, rail_map, optionals
diff --git a/setup.py b/setup.py
index cb09748e955972df03586fcf9ede33e7647cd79d..951597139909083748ad36810573ab0b2f3b47ed 100644
--- a/setup.py
+++ b/setup.py
@@ -80,6 +80,6 @@ setup(
     test_suite='tests',
     tests_require=test_requirements,
     url='https://gitlab.aicrowd.com/flatland/flatland',
-    version='2.2.2',
+    version='3.0.0',
     zip_safe=False,
 )
diff --git a/tests/test_action_plan.py b/tests/test_action_plan.py
index 9a03eb8a9f89e1edaac558e563a3c0544b4d6b5c..71a73fbc9a8f6bebb05489c3d59f1bbe41821931 100644
--- a/tests/test_action_plan.py
+++ b/tests/test_action_plan.py
@@ -6,18 +6,18 @@ from flatland.envs.observations import GlobalObsForRailEnv
 from flatland.envs.rail_env import RailEnv, RailEnvActions
 from flatland.envs.rail_generators import rail_from_grid_transition_map
 from flatland.envs.rail_trainrun_data_structures import Waypoint
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.rendertools import RenderTool, AgentRenderVariant
 from flatland.utils.simple_rail import make_simple_rail
 
 
 def test_action_plan(rendering: bool = False):
     """Tests ActionPlanReplayer: does action plan generation and replay work as expected."""
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optionals = make_simple_rail()
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(seed=77),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(seed=77),
                   number_of_agents=2,
                   obs_builder_object=GlobalObsForRailEnv(),
                   remove_agents_at_target=True
@@ -34,6 +34,10 @@ def test_action_plan(rendering: bool = False):
     for handle, agent in enumerate(env.agents):
         print("[{}] {} -> {}".format(handle, agent.initial_position, agent.target))
 
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+
     chosen_path_dict = {0: [TrainrunWaypoint(scheduled_at=0, waypoint=Waypoint(position=(3, 0), direction=3)),
                             TrainrunWaypoint(scheduled_at=2, waypoint=Waypoint(position=(3, 1), direction=1)),
                             TrainrunWaypoint(scheduled_at=3, waypoint=Waypoint(position=(3, 2), direction=1)),
diff --git a/tests/test_distance_map.py b/tests/test_distance_map.py
index c6a96fbefff68c4dbe448fc666e94317729aae6b..37cf3845c59cb9495af5c59e223857223382da78 100644
--- a/tests/test_distance_map.py
+++ b/tests/test_distance_map.py
@@ -6,7 +6,7 @@ from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 
 
 def test_walker():
@@ -25,10 +25,24 @@ def test_walker():
     rail = GridTransitionMap(width=rail_map.shape[1],
                              height=rail_map.shape[0], transitions=transitions)
     rail.grid = rail_map
+
+    city_positions = [(0,2), (0, 1)]
+    train_stations = [
+                      [( (0, 1), 0 ) ], 
+                      [( (0, 2), 0 ) ],
+                     ]
+    city_orientations = [1, 0]
+    agents_hints = {'num_agents': 1,
+                   'city_positions': city_positions,
+                   'train_stations': train_stations,
+                   'city_orientations': city_orientations
+                  }
+    optionals = {'agents_hints': agents_hints}
+
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2,
                                                        predictor=ShortestPathPredictorForRailEnv(max_depth=10)),
@@ -39,9 +53,9 @@ def test_walker():
     env.agents[0].position = (0, 1)
     env.agents[0].direction = 1
     env.agents[0].target = (0, 0)
-
     # reset to set agents from agents_static
-    env.reset(False, False)
+    # env.reset(False, False)
+    env.distance_map._compute(env.agents, env.rail)
 
     print(env.distance_map.get()[(0, *[0, 1], 1)])
     assert env.distance_map.get()[(0, *[0, 1], 1)] == 3
diff --git a/tests/test_flaltland_rail_agent_status.py b/tests/test_flaltland_rail_agent_status.py
index 05127379b78d96e8b68be2b82b8210a6ba86546b..72fc1a85853ee6dcbb3793be43118101fd2d394f 100644
--- a/tests/test_flaltland_rail_agent_status.py
+++ b/tests/test_flaltland_rail_agent_status.py
@@ -4,19 +4,24 @@ from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv, RailEnvActions
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.simple_rail import make_simple_rail
 from test_utils import ReplayConfig, Replay, run_replay_config, set_penalties_for_replay
 
 
 def test_initial_status():
     """Test that agent lifecycle works correctly ready-to-depart -> active -> done."""
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   remove_agents_at_target=False)
     env.reset()
+
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+
     set_penalties_for_replay(env)
     test_config = ReplayConfig(
         replay=[
@@ -119,13 +124,17 @@ def test_initial_status():
 
 def test_status_done_remove():
     """Test that agent lifecycle works correctly ready-to-depart -> active -> done."""
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   remove_agents_at_target=True)
     env.reset()
 
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+
     set_penalties_for_replay(env)
     test_config = ReplayConfig(
         replay=[
diff --git a/tests/test_flatland_core_transition_map.py b/tests/test_flatland_core_transition_map.py
index a569aa35534385698369980566c426cf72b7bb4b..c6fcd48d2e8e0226d3cbb69b15dd444a31adcb7d 100644
--- a/tests/test_flatland_core_transition_map.py
+++ b/tests/test_flatland_core_transition_map.py
@@ -6,7 +6,7 @@ from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.rendertools import RenderTool
 from flatland.utils.simple_rail import make_simple_rail, make_simple_rail_unconnected
 
@@ -66,11 +66,11 @@ def check_path(env, rail, position, direction, target, expected, rendering=False
 
 
 def test_path_exists(rendering=False):
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optiionals = make_simple_rail()
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optiionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   )
@@ -130,11 +130,11 @@ def test_path_exists(rendering=False):
 
 
 def test_path_not_exists(rendering=False):
-    rail, rail_map = make_simple_rail_unconnected()
+    rail, rail_map, optionals = make_simple_rail_unconnected()
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   )
diff --git a/tests/test_flatland_envs_observations.py b/tests/test_flatland_envs_observations.py
index 6e5a374d6606800b311205bd86d99600db447b91..1634ebb0819417ee10ccea226095d814d2c5bbea 100644
--- a/tests/test_flatland_envs_observations.py
+++ b/tests/test_flatland_envs_observations.py
@@ -10,7 +10,7 @@ from flatland.envs.observations import GlobalObsForRailEnv, TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv, RailEnvActions
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.rendertools import RenderTool
 from flatland.utils.simple_rail import make_simple_rail
 
@@ -18,10 +18,10 @@ from flatland.utils.simple_rail import make_simple_rail
 
 
 def test_global_obs():
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optionals = make_simple_rail()
 
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=GlobalObsForRailEnv())
 
     global_obs, info = env.reset()
@@ -91,9 +91,9 @@ def _step_along_shortest_path(env, obs_builder, rail):
 
 
 def test_reward_function_conflict(rendering=False):
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=2,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=2,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     obs_builder: TreeObsForRailEnv = env.obs_builder
     env.reset()
@@ -179,9 +179,9 @@ def test_reward_function_conflict(rendering=False):
 
 
 def test_reward_function_waiting(rendering=False):
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=2,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=2,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   remove_agents_at_target=False)
     obs_builder: TreeObsForRailEnv = env.obs_builder
diff --git a/tests/test_flatland_envs_predictions.py b/tests/test_flatland_envs_predictions.py
index c649517108597de87dd8195169b4672434590c0f..195ee9aa7856c65b0ddaf22da2f4ef5a7fea5e4b 100644
--- a/tests/test_flatland_envs_predictions.py
+++ b/tests/test_flatland_envs_predictions.py
@@ -12,7 +12,7 @@ from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_env_shortest_paths import get_shortest_paths
 from flatland.envs.rail_generators import rail_from_grid_transition_map
 from flatland.envs.rail_trainrun_data_structures import Waypoint
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.rendertools import RenderTool
 from flatland.utils.simple_rail import make_simple_rail, make_simple_rail2, make_invalid_simple_rail
 
@@ -20,12 +20,12 @@ from flatland.utils.simple_rail import make_simple_rail, make_simple_rail2, make
 
 
 def test_dummy_predictor(rendering=False):
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=DummyPredictorForRailEnv(max_depth=10)),
                   )
@@ -112,11 +112,11 @@ def test_dummy_predictor(rendering=False):
 
 
 def test_shortest_path_predictor(rendering=False):
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optionals = make_simple_rail()
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   )
@@ -133,6 +133,11 @@ def test_shortest_path_predictor(rendering=False):
     agent.status = RailAgentStatus.ACTIVE
 
     env.reset(False, False)
+    env.distance_map._compute(env.agents, env.rail)
+    
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
 
     if rendering:
         renderer = RenderTool(env, gl="PILSVG")
@@ -141,9 +146,8 @@ def test_shortest_path_predictor(rendering=False):
 
     # compute the observations and predictions
     distance_map = env.distance_map.get()
-    assert distance_map[0, agent.initial_position[0], agent.initial_position[1], agent.direction] == 5.0, \
-        "found {} instead of {}".format(
-            distance_map[agent.handle, agent.initial_position[0], agent.position[1], agent.direction], 5.0)
+    distance_on_map = distance_map[0, agent.initial_position[0], agent.initial_position[1], agent.direction]
+    assert distance_on_map == 5.0, "found {} instead of {}".format(distance_on_map, 5.0)
 
     paths = get_shortest_paths(env.distance_map)[0]
     assert paths == [
@@ -243,11 +247,11 @@ def test_shortest_path_predictor(rendering=False):
 
 
 def test_shortest_path_predictor_conflicts(rendering=False):
-    rail, rail_map = make_invalid_simple_rail()
+    rail, rail_map, optionals = make_invalid_simple_rail()
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=2,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()),
                   )
diff --git a/tests/test_flatland_envs_rail_env.py b/tests/test_flatland_envs_rail_env.py
index 0c865502f94b624ca9712a20cb83af72b0310357..4502ca678f102f0a03a642f22f05db5656eb573e 100644
--- a/tests/test_flatland_envs_rail_env.py
+++ b/tests/test_flatland_envs_rail_env.py
@@ -9,9 +9,9 @@ from flatland.envs.agent_utils import EnvAgent
 from flatland.envs.observations import GlobalObsForRailEnv, TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv, RailEnvActions
-from flatland.envs.rail_generators import complex_rail_generator, rail_from_file
+from flatland.envs.rail_generators import sparse_rail_generator, rail_from_file
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator, complex_schedule_generator, schedule_from_file
+from flatland.envs.line_generators import sparse_line_generator, line_from_file
 from flatland.utils.simple_rail import make_simple_rail
 from flatland.envs.persistence import RailEnvPersister
 from flatland.utils.rendertools import RenderTool
@@ -36,10 +36,11 @@ def test_load_env():
 
 
 def test_save_load():
-    env = RailEnv(width=10, height=10,
-                  rail_generator=complex_rail_generator(nr_start_goal=2, nr_extra=5, min_dist=6, seed=1),
-                  schedule_generator=complex_schedule_generator(), number_of_agents=2)
+    env = RailEnv(width=30, height=30,
+                  rail_generator=sparse_rail_generator(seed=1),
+                  line_generator=sparse_line_generator(), number_of_agents=2)
     env.reset()
+
     agent_1_pos = env.agents[0].position
     agent_1_dir = env.agents[0].direction
     agent_1_tar = env.agents[0].target
@@ -54,8 +55,8 @@ def test_save_load():
 
     #env.load("test_save.dat")
     env, env_dict = RailEnvPersister.load_new("tmp/test_save.pkl")
-    assert (env.width == 10)
-    assert (env.height == 10)
+    assert (env.width == 30)
+    assert (env.height == 30)
     assert (len(env.agents) == 2)
     assert (agent_1_pos == env.agents[0].position)
     assert (agent_1_dir == env.agents[0].direction)
@@ -66,9 +67,9 @@ def test_save_load():
 
 
 def test_save_load_mpk():
-    env = RailEnv(width=10, height=10,
-                  rail_generator=complex_rail_generator(nr_start_goal=2, nr_extra=5, min_dist=6, seed=1),
-                  schedule_generator=complex_schedule_generator(), number_of_agents=2)
+    env = RailEnv(width=30, height=30,
+                  rail_generator=sparse_rail_generator(seed=1),
+                  line_generator=sparse_line_generator(), number_of_agents=2)
     env.reset()
 
     os.makedirs("tmp", exist_ok=True)
@@ -120,7 +121,7 @@ def test_rail_environment_single_agent(show=False):
         rail = GridTransitionMap(width=3, height=3, transitions=transitions)
         rail.grid = rail_map
         rail_env = RailEnv(width=3, height=3, rail_generator=rail_from_grid_transition_map(rail),
-                        schedule_generator=random_schedule_generator(), number_of_agents=1,
+                        line_generator=sparse_line_generator(), number_of_agents=1,
                         obs_builder_object=GlobalObsForRailEnv())
     else:
         rail_env, env_dict = RailEnvPersister.load_new("test_env_loop.pkl", "env_data.tests")
@@ -203,7 +204,7 @@ def test_rail_environment_single_agent(show=False):
 
             rail_env.agents[0].direction = 0
 
-            # JW - to avoid problem with random_schedule_generator.
+            # JW - to avoid problem with sparse_line_generator.
             #rail_env.agents[0].position = (1,2)
 
             iStep = 0
@@ -246,7 +247,7 @@ def test_dead_end():
     rail.grid = rail_map
     rail_env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0],
                        rail_generator=rail_from_grid_transition_map(rail),
-                       schedule_generator=random_schedule_generator(), number_of_agents=1,
+                       line_generator=sparse_line_generator(), number_of_agents=1,
                        obs_builder_object=GlobalObsForRailEnv())
 
     # We try the configuration in the 4 directions:
@@ -269,7 +270,7 @@ def test_dead_end():
     rail.grid = rail_map
     rail_env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0],
                        rail_generator=rail_from_grid_transition_map(rail),
-                       schedule_generator=random_schedule_generator(), number_of_agents=1,
+                       line_generator=sparse_line_generator(), number_of_agents=1,
                        obs_builder_object=GlobalObsForRailEnv())
 
     rail_env.reset()
@@ -282,9 +283,9 @@ def test_dead_end():
 
 
 def test_get_entry_directions():
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
 
@@ -316,10 +317,10 @@ def test_rail_env_reset():
 
     # Test to save and load file.
 
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optionals = make_simple_rail()
 
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=3,
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=3,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
 
@@ -331,7 +332,7 @@ def test_rail_env_reset():
     agents_initial = env.agents
 
     #env2 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name),
-    #               schedule_generator=schedule_from_file(file_name), number_of_agents=1,
+    #               line_generator=line_from_file(file_name), number_of_agents=1,
     #               obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     #env2.reset(False, False, False)
     env2, env2_dict = RailEnvPersister.load_new(file_name)
@@ -343,7 +344,7 @@ def test_rail_env_reset():
     assert agents_initial == agents_loaded
 
     env3 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name),
-                   schedule_generator=schedule_from_file(file_name), number_of_agents=1,
+                   line_generator=line_from_file(file_name), number_of_agents=1,
                    obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env3.reset(False, True, False)
     rails_loaded = env3.rail.grid
@@ -353,7 +354,7 @@ def test_rail_env_reset():
     assert agents_initial == agents_loaded
 
     env4 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name),
-                   schedule_generator=schedule_from_file(file_name), number_of_agents=1,
+                   line_generator=line_from_file(file_name), number_of_agents=1,
                    obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env4.reset(True, False, False)
     rails_loaded = env4.rail.grid
diff --git a/tests/test_flatland_envs_rail_env_shortest_paths.py b/tests/test_flatland_envs_rail_env_shortest_paths.py
index 84504cf2f2be1867e450e3520d566fb1eee55cf9..5825e412a942368e3ce0566ce3d875c8cf88f601 100644
--- a/tests/test_flatland_envs_rail_env_shortest_paths.py
+++ b/tests/test_flatland_envs_rail_env_shortest_paths.py
@@ -9,20 +9,24 @@ from flatland.envs.rail_env_shortest_paths import get_shortest_paths, get_k_shor
 from flatland.envs.rail_env_utils import load_flatland_environment_from_file
 from flatland.envs.rail_generators import rail_from_grid_transition_map
 from flatland.envs.rail_trainrun_data_structures import Waypoint
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.rendertools import RenderTool
 from flatland.utils.simple_rail import make_disconnected_simple_rail, make_simple_rail_with_alternatives
 from flatland.envs.persistence import RailEnvPersister
 
 
 def test_get_shortest_paths_unreachable():
-    rail, rail_map = make_disconnected_simple_rail()
+    rail, rail_map, optionals = make_disconnected_simple_rail()
 
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=GlobalObsForRailEnv())
     env.reset()
 
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+
     # set the initial position
     agent = env.agents[0]
     agent.position = (3, 1)  # west dead-end
@@ -36,7 +40,7 @@ def test_get_shortest_paths_unreachable():
     actual = get_shortest_paths(env.distance_map)
     expected = {0: None}
 
-    assert actual == expected, "actual={},expected={}".format(actual, expected)
+    assert actual[0] == expected[0], "actual={},expected={}".format(actual[0], expected[0])
 
 
 # todo file test_002.pkl has to be generated automatically
@@ -233,12 +237,12 @@ def test_get_shortest_paths_agent_handle():
 
 
 def test_get_k_shortest_paths(rendering=False):
-    rail, rail_map = make_simple_rail_with_alternatives()
+    rail, rail_map, optionals = make_simple_rail_with_alternatives()
 
     env = RailEnv(width=rail_map.shape[1],
                   height=rail_map.shape[0],
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   obs_builder_object=GlobalObsForRailEnv(),
                   )
diff --git a/tests/test_flatland_envs_sparse_rail_generator.py b/tests/test_flatland_envs_sparse_rail_generator.py
index c6e151b36830af23d566837d8fe1f33877bf69c6..74e71daced5cde123f7b25054b264ebeee816888 100644
--- a/tests/test_flatland_envs_sparse_rail_generator.py
+++ b/tests/test_flatland_envs_sparse_rail_generator.py
@@ -7,7 +7,7 @@ from flatland.core.grid.grid_utils import Vec2dOperations as Vec2d
 from flatland.envs.observations import GlobalObsForRailEnv
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import sparse_rail_generator
-from flatland.envs.schedule_generators import sparse_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.rendertools import RenderTool
 
 
@@ -17,7 +17,7 @@ def test_sparse_rail_generator():
                                                                             seed=5,
                                                                             grid_mode=False
                                                                             ),
-                  schedule_generator=sparse_schedule_generator(), number_of_agents=10,
+                  line_generator=sparse_line_generator(), number_of_agents=10,
                   obs_builder_object=GlobalObsForRailEnv())
     env.reset(False, False, True)
     for r in range(env.height):
@@ -602,7 +602,7 @@ def test_sparse_rail_generator_deterministic():
                                                                             seed=215545,  # Random seed
                                                                             grid_mode=True
                                                                             ),
-                  schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=1)
+                  line_generator=sparse_line_generator(speed_ration_map), number_of_agents=1)
     env.reset()
     # for r in range(env.height):
     #  for c in range(env.width):
@@ -1371,7 +1371,7 @@ def test_rail_env_action_required_info():
         max_rails_between_cities=3,
         seed=5,  # Random seed
         grid_mode=False  # Ordered distribution of nodes
-    ), schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=10,
+    ), line_generator=sparse_line_generator(speed_ration_map), number_of_agents=10,
                                 obs_builder_object=GlobalObsForRailEnv(), remove_agents_at_target=False)
 
     env_only_if_action_required = RailEnv(width=50, height=50, rail_generator=sparse_rail_generator(
@@ -1380,7 +1380,7 @@ def test_rail_env_action_required_info():
         seed=5,  # Random seed
         grid_mode=False
         # Ordered distribution of nodes
-    ), schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=10,
+    ), line_generator=sparse_line_generator(speed_ration_map), number_of_agents=10,
                                           obs_builder_object=GlobalObsForRailEnv(), remove_agents_at_target=False)
     env_renderer = RenderTool(env_always_action, gl="PILSVG", )
 
@@ -1442,7 +1442,7 @@ def test_rail_env_malfunction_speed_info():
                                                                             seed=5,
                                                                             grid_mode=False
                                                                             ),
-                  schedule_generator=sparse_schedule_generator(), number_of_agents=10,
+                  line_generator=sparse_line_generator(), number_of_agents=10,
                   obs_builder_object=GlobalObsForRailEnv())
     env.reset(False, False, True)
 
@@ -1476,7 +1476,7 @@ def test_sparse_generator_with_too_man_cities_does_not_break_down():
         max_rails_between_cities=3,
         seed=5,
         grid_mode=False
-    ), schedule_generator=sparse_schedule_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv())
+    ), line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv())
 
 
 def test_sparse_generator_with_illegal_params_aborts():
@@ -1489,7 +1489,7 @@ def test_sparse_generator_with_illegal_params_aborts():
             max_rails_between_cities=3,
             seed=5,
             grid_mode=False
-        ), schedule_generator=sparse_schedule_generator(), number_of_agents=10,
+        ), line_generator=sparse_line_generator(), number_of_agents=10,
                 obs_builder_object=GlobalObsForRailEnv()).reset()
 
     with unittest.TestCase.assertRaises(test_sparse_generator_with_illegal_params_aborts, ValueError):
@@ -1498,7 +1498,7 @@ def test_sparse_generator_with_illegal_params_aborts():
             max_rails_between_cities=3,
             seed=5,
             grid_mode=False
-        ), schedule_generator=sparse_schedule_generator(), number_of_agents=10,
+        ), line_generator=sparse_line_generator(), number_of_agents=10,
                 obs_builder_object=GlobalObsForRailEnv()).reset()
 
 
@@ -1512,10 +1512,10 @@ def test_sparse_generator_changes_to_grid_mode():
     rail_env = RailEnv(width=10, height=20, rail_generator=sparse_rail_generator(
         max_num_cities=100,
         max_rails_between_cities=2,
-        max_rails_in_city=2,
+        max_rail_pairs_in_city=1,
         seed=15,
         grid_mode=False
-    ), schedule_generator=sparse_schedule_generator(), number_of_agents=10,
+    ), line_generator=sparse_line_generator(), number_of_agents=10,
                        obs_builder_object=GlobalObsForRailEnv())
     for test_run in range(10):
         with warnings.catch_warnings(record=True) as w:
diff --git a/tests/test_flatland_line_from_file.py b/tests/test_flatland_line_from_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7e9738e3624dce4d843d7c0345807cbd6708159
--- /dev/null
+++ b/tests/test_flatland_line_from_file.py
@@ -0,0 +1,47 @@
+from test_utils import create_and_save_env
+
+from flatland.envs.rail_env import RailEnv
+from flatland.envs.rail_generators import sparse_rail_generator, rail_from_file
+from flatland.envs.line_generators import sparse_line_generator, line_from_file
+
+
+def test_line_from_file_sparse():
+    """
+    Test to see that all parameters are loaded as expected
+    Returns
+    -------
+
+    """
+    # Different agent types (trains) with different speeds.
+    speed_ration_map = {1.: 0.25,  # Fast passenger train
+                        1. / 2.: 0.25,  # Fast freight train
+                        1. / 3.: 0.25,  # Slow commuter train
+                        1. / 4.: 0.25}  # Slow freight train
+
+    # Generate Sparse test env
+    rail_generator = sparse_rail_generator(max_num_cities=5,
+                                           seed=1,
+                                           grid_mode=False,
+                                           max_rails_between_cities=3,
+                                           max_rail_pairs_in_city=3,
+                                           )
+    line_generator = sparse_line_generator(speed_ration_map)
+
+    env = create_and_save_env(file_name="./sparse_env_test.pkl", rail_generator=rail_generator,
+                        line_generator=line_generator)
+    old_num_steps = env._max_episode_steps
+    old_num_agents = len(env.agents)
+
+
+    # Sparse generator
+    rail_generator = rail_from_file("./sparse_env_test.pkl")
+    line_generator = line_from_file("./sparse_env_test.pkl")
+    sparse_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator,
+                                   line_generator=line_generator)
+    sparse_env_from_file.reset(True, True)
+
+    # Assert loaded agent number is correct
+    assert sparse_env_from_file.get_num_agents() == old_num_agents
+
+    # Assert max steps is correct
+    assert sparse_env_from_file._max_episode_steps == old_num_steps
\ No newline at end of file
diff --git a/tests/test_flatland_malfunction.py b/tests/test_flatland_malfunction.py
index eaa3112708f3f0e5d255b7e454078d9a59e7ca22..341ff2560b80dbe0734fb8cd02dc2f5592fc59d1 100644
--- a/tests/test_flatland_malfunction.py
+++ b/tests/test_flatland_malfunction.py
@@ -10,7 +10,7 @@ from flatland.envs.agent_utils import RailAgentStatus
 from flatland.envs.malfunction_generators import malfunction_from_params, MalfunctionParameters
 from flatland.envs.rail_env import RailEnv, RailEnvActions
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.simple_rail import make_simple_rail2
 from test_utils import Replay, ReplayConfig, run_replay_config, set_penalties_for_replay
 
@@ -72,12 +72,12 @@ def test_malfunction_process():
                                             max_duration=3  # Max duration of malfunction
                                             )
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   obs_builder_object=SingleAgentNavigationObs()
@@ -90,6 +90,9 @@ def test_malfunction_process():
 
     # Move target to unreachable position in order to not interfere with test
     env.agents[0].target = (0, 0)
+    
+    # Add in max episode steps because scheudule generator sets it to 0 for dummy data
+    env._max_episode_steps = 200
     for step in range(100):
         actions = {}
 
@@ -97,6 +100,8 @@ def test_malfunction_process():
             actions[i] = np.argmax(obs[i]) + 1
 
         obs, all_rewards, done, _ = env.step(actions)
+        if done["__all__"]:
+            break
 
         if env.agents[0].malfunction_data['malfunction'] > 0:
             agent_malfunctioning = True
@@ -109,9 +114,9 @@ def test_malfunction_process():
 
         agent_old_position = env.agents[0].position
         total_down_time += env.agents[0].malfunction_data['malfunction']
-
     # Check that the appropriate number of malfunctions is achieved
-    assert env.agents[0].malfunction_data['nr_malfunctions'] == 23, "Actual {}".format(
+    # Dipam: The number of malfunctions varies by seed
+    assert env.agents[0].malfunction_data['nr_malfunctions'] == 21, "Actual {}".format(
         env.agents[0].malfunction_data['nr_malfunctions'])
 
     # Check that malfunctioning data was standing around
@@ -126,12 +131,12 @@ def test_malfunction_process_statistically():
                                             max_duration=5  # Max duration of malfunction
                                             )
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=10,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   obs_builder_object=SingleAgentNavigationObs()
@@ -173,12 +178,12 @@ def test_malfunction_before_entry():
                                             max_duration=10  # Max duration of malfunction
                                             )
 
-    rail, rail_map = make_simple_rail2()
-
+    rail, rail_map, optionals = make_simple_rail2()
+    
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=10,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   obs_builder_object=SingleAgentNavigationObs()
@@ -213,7 +218,7 @@ def test_malfunction_values_and_behavior():
     """
     # Set fixed malfunction duration for this test
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
     action_dict: Dict[int, RailEnvActions] = {}
     stochastic_data = MalfunctionParameters(malfunction_rate=1/0.001,  # Rate of malfunction occurence
                                             min_duration=10,  # Minimal duration of malfunction
@@ -221,8 +226,8 @@ def test_malfunction_values_and_behavior():
                                             )
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   obs_builder_object=SingleAgentNavigationObs()
@@ -246,12 +251,12 @@ def test_initial_malfunction():
                                             max_duration=5  # Max duration of malfunction
                                             )
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(seed=10),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(seed=10),
                   number_of_agents=1,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   # Malfunction data generator
@@ -313,10 +318,10 @@ def test_initial_malfunction():
 
 
 def test_initial_malfunction_stop_moving():
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
-    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=SingleAgentNavigationObs())
     env.reset()
 
@@ -395,12 +400,12 @@ def test_initial_malfunction_do_nothing():
                                             max_duration=5  # Max duration of malfunction
                                             )
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=1,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   # Malfunction data generator
@@ -475,9 +480,9 @@ def test_initial_malfunction_do_nothing():
 def tests_random_interference_from_outside():
     """Tests that malfunctions are produced by stochastic_data!"""
     # Set fixed malfunction duration for this test
-    rail, rail_map = make_simple_rail2()
-    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(seed=2), number_of_agents=1, random_seed=1)
+    rail, rail_map, optionals = make_simple_rail2()
+    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(seed=2), number_of_agents=1, random_seed=1)
     env.reset()
     env.agents[0].speed_data['speed'] = 0.33
     env.reset(False, False, False, random_seed=10)
@@ -497,11 +502,11 @@ def tests_random_interference_from_outside():
     # Run the same test as above but with an external random generator running
     # Check that the reward stays the same
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
     random.seed(47)
     np.random.seed(1234)
-    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(seed=2), number_of_agents=1, random_seed=1)
+    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(seed=2), number_of_agents=1, random_seed=1)
     env.reset()
     env.agents[0].speed_data['speed'] = 0.33
     env.reset(False, False, False, random_seed=10)
@@ -530,10 +535,10 @@ def test_last_malfunction_step():
 
     # Set fixed malfunction duration for this test
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
-    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(seed=2), number_of_agents=1, random_seed=1)
+    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(seed=2), number_of_agents=1, random_seed=1)
     env.reset()
     env.agents[0].speed_data['speed'] = 1. / 3.
     env.agents[0].target = (0, 0)
diff --git a/tests/test_flatland_multiprocessing.py b/tests/test_flatland_multiprocessing.py
index 23cfeeacdf160ce7a8389e4a1b6f42078650ade2..64366566362cd7aa4dd581179b395515b4d6ba7b 100644
--- a/tests/test_flatland_multiprocessing.py
+++ b/tests/test_flatland_multiprocessing.py
@@ -6,7 +6,7 @@ from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.simple_rail import make_simple_rail
 
 """Tests for `flatland` package."""
@@ -14,12 +14,13 @@ from flatland.utils.simple_rail import make_simple_rail
 
 def test_multiprocessing_tree_obs():
     number_of_agents = 5
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optionals = make_simple_rail()
+    optionals['agents_hints']['num_agents'] = number_of_agents
 
     obs_builder = TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())
 
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=number_of_agents,
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=number_of_agents,
                   obs_builder_object=obs_builder)
     env.reset(True, True)
 
diff --git a/tests/test_flatland_schedule_from_file.py b/tests/test_flatland_schedule_from_file.py
deleted file mode 100644
index 52a64a19343ad320d8c26922ed5e42e400821c73..0000000000000000000000000000000000000000
--- a/tests/test_flatland_schedule_from_file.py
+++ /dev/null
@@ -1,125 +0,0 @@
-from test_utils import create_and_save_env
-
-from flatland.envs.rail_env import RailEnv
-from flatland.envs.rail_generators import sparse_rail_generator, random_rail_generator, complex_rail_generator, \
-    rail_from_file
-from flatland.envs.schedule_generators import sparse_schedule_generator, random_schedule_generator, \
-    complex_schedule_generator, schedule_from_file
-
-
-def test_schedule_from_file_sparse():
-    """
-    Test to see that all parameters are loaded as expected
-    Returns
-    -------
-
-    """
-    # Different agent types (trains) with different speeds.
-    speed_ration_map = {1.: 0.25,  # Fast passenger train
-                        1. / 2.: 0.25,  # Fast freight train
-                        1. / 3.: 0.25,  # Slow commuter train
-                        1. / 4.: 0.25}  # Slow freight train
-
-    # Generate Sparse test env
-    rail_generator = sparse_rail_generator(max_num_cities=5,
-                                           seed=1,
-                                           grid_mode=False,
-                                           max_rails_between_cities=3,
-                                           max_rails_in_city=6,
-                                           )
-    schedule_generator = sparse_schedule_generator(speed_ration_map)
-
-    create_and_save_env(file_name="./sparse_env_test.pkl", rail_generator=rail_generator,
-                        schedule_generator=schedule_generator)
-
-
-    # Sparse generator
-    rail_generator = rail_from_file("./sparse_env_test.pkl")
-    schedule_generator = schedule_from_file("./sparse_env_test.pkl")
-    sparse_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator,
-                                   schedule_generator=schedule_generator)
-    sparse_env_from_file.reset(True, True)
-
-    # Assert loaded agent number is correct
-    assert sparse_env_from_file.get_num_agents() == 10
-
-    # Assert max steps is correct
-    assert sparse_env_from_file._max_episode_steps == 500
-
-
-
-def test_schedule_from_file_random():
-    """
-    Test to see that all parameters are loaded as expected
-    Returns
-    -------
-
-    """
-    # Different agent types (trains) with different speeds.
-    speed_ration_map = {1.: 0.25,  # Fast passenger train
-                        1. / 2.: 0.25,  # Fast freight train
-                        1. / 3.: 0.25,  # Slow commuter train
-                        1. / 4.: 0.25}  # Slow freight train
-
-    # Generate random test env
-    rail_generator = random_rail_generator()
-    schedule_generator = random_schedule_generator(speed_ration_map)
-
-    create_and_save_env(file_name="./random_env_test.pkl", rail_generator=rail_generator,
-                        schedule_generator=schedule_generator)
-
-
-    # Random generator
-    rail_generator = rail_from_file("./random_env_test.pkl")
-    schedule_generator = schedule_from_file("./random_env_test.pkl")
-    random_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator,
-                                   schedule_generator=schedule_generator)
-    random_env_from_file.reset(True, True)
-
-    # Assert loaded agent number is correct
-    assert random_env_from_file.get_num_agents() == 10
-
-    # Assert max steps is correct
-    assert random_env_from_file._max_episode_steps == 1350
-
-
-
-
-def test_schedule_from_file_complex():
-    """
-    Test to see that all parameters are loaded as expected
-    Returns
-    -------
-
-    """
-    # Different agent types (trains) with different speeds.
-    speed_ration_map = {1.: 0.25,  # Fast passenger train
-                        1. / 2.: 0.25,  # Fast freight train
-                        1. / 3.: 0.25,  # Slow commuter train
-                        1. / 4.: 0.25}  # Slow freight train
-
-    # Generate complex test env
-    rail_generator = complex_rail_generator(nr_start_goal=10,
-                                            nr_extra=1,
-                                            min_dist=8,
-                                            max_dist=99999)
-    schedule_generator = complex_schedule_generator(speed_ration_map)
-
-    create_and_save_env(file_name="./complex_env_test.pkl", rail_generator=rail_generator,
-                        schedule_generator=schedule_generator)
-
-    # Load the different envs and check the parameters
-
-
-    # Complex generator
-    rail_generator = rail_from_file("./complex_env_test.pkl")
-    schedule_generator = schedule_from_file("./complex_env_test.pkl")
-    complex_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator,
-                                    schedule_generator=schedule_generator)
-    complex_env_from_file.reset(True, True)
-
-    # Assert loaded agent number is correct
-    assert complex_env_from_file.get_num_agents() == 10
-
-    # Assert max steps is correct
-    assert complex_env_from_file._max_episode_steps == 1350
diff --git a/tests/test_generators.py b/tests/test_generators.py
index c723c194f179efcc191f80fb93a3e5370e5469c9..0a408444ae9f25ae5e6d904c91ad6e461fec1304 100644
--- a/tests/test_generators.py
+++ b/tests/test_generators.py
@@ -6,16 +6,14 @@ import numpy as np
 from flatland.envs.observations import TreeObsForRailEnv, GlobalObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv
-from flatland.envs.rail_generators import rail_from_grid_transition_map, rail_from_file, complex_rail_generator, \
-    random_rail_generator, empty_rail_generator
-from flatland.envs.schedule_generators import random_schedule_generator, complex_schedule_generator, \
-    schedule_from_file
+from flatland.envs.rail_generators import rail_from_grid_transition_map, rail_from_file, empty_rail_generator
+from flatland.envs.line_generators import sparse_line_generator, line_from_file
 from flatland.utils.simple_rail import make_simple_rail
 from flatland.envs.persistence import RailEnvPersister
 
 
 def test_empty_rail_generator():
-    n_agents = 1
+    n_agents = 2
     x_dim = 5
     y_dim = 10
 
@@ -30,61 +28,11 @@ def test_empty_rail_generator():
     assert env.get_num_agents() == 0
 
 
-def test_random_rail_generator():
-    n_agents = 1
-    x_dim = 5
-    y_dim = 10
-
-    # Check that a random level at with correct parameters is generated
-    env = RailEnv(width=x_dim, height=y_dim, rail_generator=random_rail_generator(), number_of_agents=n_agents)
-    env.reset()
-    assert env.rail.grid.shape == (y_dim, x_dim)
-    assert env.get_num_agents() == n_agents
-
-
-def test_complex_rail_generator():
-    n_agents = 10
-    n_start = 2
-    x_dim = 10
-    y_dim = 10
-    min_dist = 4
-
-    # Check that agent number is changed to fit generated level
-    env = RailEnv(width=x_dim, height=y_dim,
-                  rail_generator=complex_rail_generator(nr_start_goal=n_start, nr_extra=0, min_dist=min_dist),
-                  schedule_generator=complex_schedule_generator(), number_of_agents=n_agents)
-    env.reset()
-    assert env.get_num_agents() == 2
-    assert env.rail.grid.shape == (y_dim, x_dim)
-
-    min_dist = 2 * x_dim
-
-    # Check that no agents are generated when level cannot be generated
-    env = RailEnv(width=x_dim, height=y_dim,
-                  rail_generator=complex_rail_generator(nr_start_goal=n_start, nr_extra=0, min_dist=min_dist),
-                  schedule_generator=complex_schedule_generator(), number_of_agents=n_agents)
-    env.reset()
-    assert env.get_num_agents() == 0
-    assert env.rail.grid.shape == (y_dim, x_dim)
-
-    # Check that everything stays the same when correct parameters are given
-    min_dist = 2
-    n_start = 5
-    n_agents = 5
-
-    env = RailEnv(width=x_dim, height=y_dim,
-                  rail_generator=complex_rail_generator(nr_start_goal=n_start, nr_extra=0, min_dist=min_dist),
-                  schedule_generator=complex_schedule_generator(), number_of_agents=n_agents)
-    env.reset()
-    assert env.get_num_agents() == n_agents
-    assert env.rail.grid.shape == (y_dim, x_dim)
-
-
 def test_rail_from_grid_transition_map():
-    rail, rail_map = make_simple_rail()
-    n_agents = 3
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=n_agents)
+    rail, rail_map, optionals = make_simple_rail()
+    n_agents = 4
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=n_agents)
     env.reset(False, False, True)
     nr_rail_elements = np.count_nonzero(env.rail.grid)
 
@@ -103,10 +51,10 @@ def tests_rail_from_file():
 
     # Test to save and load file with distance map.
 
-    rail, rail_map = make_simple_rail()
+    rail, rail_map, optionals = make_simple_rail()
 
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=3,
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=3,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
     #env.save(file_name)
@@ -116,7 +64,7 @@ def tests_rail_from_file():
     agents_initial = env.agents
 
     env = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name),
-                  schedule_generator=schedule_from_file(file_name), number_of_agents=1,
+                  line_generator=line_from_file(file_name), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
     rails_loaded = env.rail.grid
@@ -134,7 +82,7 @@ def tests_rail_from_file():
     file_name_2 = "test_without_distance_map.pkl"
 
     env2 = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0],
-                   rail_generator=rail_from_grid_transition_map(rail), schedule_generator=random_schedule_generator(),
+                   rail_generator=rail_from_grid_transition_map(rail), line_generator=sparse_line_generator(),
                    number_of_agents=3, obs_builder_object=GlobalObsForRailEnv())
     env2.reset()
     #env2.save(file_name_2)
@@ -144,7 +92,7 @@ def tests_rail_from_file():
     agents_initial_2 = env2.agents
 
     env2 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name_2),
-                   schedule_generator=schedule_from_file(file_name_2), number_of_agents=1,
+                   line_generator=line_from_file(file_name_2), number_of_agents=1,
                    obs_builder_object=GlobalObsForRailEnv())
     env2.reset()
     rails_loaded_2 = env2.rail.grid
@@ -157,7 +105,7 @@ def tests_rail_from_file():
     # Test to save with distance map and load without
 
     env3 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name),
-                   schedule_generator=schedule_from_file(file_name), number_of_agents=1,
+                   line_generator=line_from_file(file_name), number_of_agents=1,
                    obs_builder_object=GlobalObsForRailEnv())
     env3.reset()
     rails_loaded_3 = env3.rail.grid
@@ -172,7 +120,7 @@ def tests_rail_from_file():
     env4 = RailEnv(width=1,
                    height=1,
                    rail_generator=rail_from_file(file_name_2),
-                   schedule_generator=schedule_from_file(file_name_2),
+                   line_generator=line_from_file(file_name_2),
                    number_of_agents=1,
                    obs_builder_object=TreeObsForRailEnv(max_depth=2),
                    )
diff --git a/tests/test_global_observation.py b/tests/test_global_observation.py
index 0cd2ac1b5fb0af5583e66c931817ccd7ce8b7f71..851d849d1246773d7d06b5f38ed0eef820f74a56 100644
--- a/tests/test_global_observation.py
+++ b/tests/test_global_observation.py
@@ -4,7 +4,7 @@ from flatland.envs.agent_utils import EnvAgent, RailAgentStatus
 from flatland.envs.observations import GlobalObsForRailEnv
 from flatland.envs.rail_env import RailEnv, RailEnvActions
 from flatland.envs.rail_generators import sparse_rail_generator
-from flatland.envs.schedule_generators import sparse_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 
 
 def test_get_global_observation():
@@ -26,10 +26,14 @@ def test_get_global_observation():
                                                                             seed=15,
                                                                             grid_mode=False
                                                                             ),
-                  schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=number_of_agents,
+                  line_generator=sparse_line_generator(speed_ration_map), number_of_agents=number_of_agents,
                   obs_builder_object=GlobalObsForRailEnv())
     env.reset()
 
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+
     obs, all_rewards, done, _ = env.step({i: RailEnvActions.MOVE_FORWARD for i in range(number_of_agents)})
     for i in range(len(env.agents)):
         agent: EnvAgent = env.agents[i]
diff --git a/tests/test_malfunction_generators.py b/tests/test_malfunction_generators.py
index 2593361e5922dd3078b614997e6306c1ab5549d5..af5ffeb505b831c58dd15743ba71ea25510666a7 100644
--- a/tests/test_malfunction_generators.py
+++ b/tests/test_malfunction_generators.py
@@ -2,7 +2,7 @@ from flatland.envs.malfunction_generators import malfunction_from_params, malfun
     single_malfunction_generator, MalfunctionParameters
 from flatland.envs.rail_env import RailEnv, RailEnvActions
 from flatland.envs.rail_generators import rail_from_grid_transition_map
-from flatland.envs.schedule_generators import random_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.simple_rail import make_simple_rail2
 from flatland.envs.persistence import RailEnvPersister
 
@@ -17,12 +17,12 @@ def test_malfanction_from_params():
                                             min_duration=2,  # Minimal duration of malfunction
                                             max_duration=5  # Max duration of malfunction
                                             )
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=10,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data)
                   )
@@ -44,12 +44,12 @@ def test_malfanction_to_and_from_file():
                                             max_duration=5  # Max duration of malfunction
                                             )
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=10,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data)
                   )
@@ -61,8 +61,8 @@ def test_malfanction_to_and_from_file():
     malfunction_generator, malfunction_process_data = malfunction_from_file("./malfunction_saving_loading_tests.pkl")
     env2 = RailEnv(width=25,
                    height=30,
-                   rail_generator=rail_from_grid_transition_map(rail),
-                   schedule_generator=random_schedule_generator(),
+                   rail_generator=rail_from_grid_transition_map(rail, optionals),
+                   line_generator=sparse_line_generator(),
                    number_of_agents=10,
                    malfunction_generator_and_process_data=malfunction_from_params(stochastic_data)
                    )
@@ -83,11 +83,11 @@ def test_single_malfunction_generator():
 
     """
 
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
     env = RailEnv(width=25,
                   height=30,
-                  rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(),
+                  rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(),
                   number_of_agents=10,
                   malfunction_generator_and_process_data=single_malfunction_generator(earlierst_malfunction=10,
                                                                                       malfunction_duration=5)
diff --git a/tests/test_multi_speed.py b/tests/test_multi_speed.py
index 0467ce5ff34c12e98f854bc44a0f42caa4ee3649..172e14047c4b9a3d509139be0e3875ca84b8712d 100644
--- a/tests/test_multi_speed.py
+++ b/tests/test_multi_speed.py
@@ -4,13 +4,13 @@ from flatland.core.grid.grid4 import Grid4TransitionsEnum
 from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv, RailEnvActions
-from flatland.envs.rail_generators import complex_rail_generator, rail_from_grid_transition_map
-from flatland.envs.schedule_generators import complex_schedule_generator, random_schedule_generator
+from flatland.envs.rail_generators import sparse_rail_generator, rail_from_grid_transition_map
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.simple_rail import make_simple_rail
 from test_utils import ReplayConfig, Replay, run_replay_config, set_penalties_for_replay
 
 
-# Use the complex_rail_generator to generate feasible network configurations with corresponding tasks
+# Use the sparse_rail_generator to generate feasible network configurations with corresponding tasks
 # Training on simple small tasks is the best way to get familiar with the environment
 #
 
@@ -48,9 +48,9 @@ class RandomAgent:
 
 def test_multi_speed_init():
     env = RailEnv(width=50, height=50,
-                  rail_generator=complex_rail_generator(nr_start_goal=10, nr_extra=1, min_dist=8, max_dist=99999,
-                                                        seed=1), schedule_generator=complex_schedule_generator(),
-                  number_of_agents=5)
+                  rail_generator=sparse_rail_generator(seed=1), line_generator=sparse_line_generator(),
+                  number_of_agents=6)
+    
     # Initialize the agent with the parameters corresponding to the environment and observation_builder
     agent = RandomAgent(218, 4)
 
@@ -92,9 +92,9 @@ def test_multi_speed_init():
 
 def test_multispeed_actions_no_malfunction_no_blocking():
     """Test that actions are correctly performed on cell exit for a single agent."""
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
 
@@ -192,11 +192,17 @@ def test_multispeed_actions_no_malfunction_no_blocking():
 
 def test_multispeed_actions_no_malfunction_blocking():
     """The second agent blocks the first because it is slower."""
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=2,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=2,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
+    
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+    
+
     set_penalties_for_replay(env)
     test_configs = [
         ReplayConfig(
@@ -376,12 +382,16 @@ def test_multispeed_actions_no_malfunction_blocking():
 
 def test_multispeed_actions_malfunction_no_blocking():
     """Test on a single agent whether action on cell exit work correctly despite malfunction."""
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
-
+    
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
+    
     set_penalties_for_replay(env)
     test_config = ReplayConfig(
         replay=[
@@ -510,11 +520,15 @@ def test_multispeed_actions_malfunction_no_blocking():
 # TODO invalid action penalty seems only given when forward is not possible - is this the intended behaviour?
 def test_multispeed_actions_no_malfunction_invalid_actions():
     """Test that actions are correctly performed on cell exit for a single agent."""
-    rail, rail_map = make_simple_rail()
-    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(), number_of_agents=1,
+    rail, rail_map, optionals = make_simple_rail()
+    env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(), number_of_agents=1,
                   obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
     env.reset()
+    
+    # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART
+    for _ in range(max([agent.earliest_departure for agent in env.agents])):
+        env.step({}) # DO_NOTHING for all agents
 
     set_penalties_for_replay(env)
     test_config = ReplayConfig(
diff --git a/tests/test_random_seeding.py b/tests/test_random_seeding.py
index 17a658f0d939f07dd093f518939c8a6cde54a526..ef29e016d0bc4e0b2b08b8c75461f3b2f9346bd9 100644
--- a/tests/test_random_seeding.py
+++ b/tests/test_random_seeding.py
@@ -4,18 +4,18 @@ from flatland.envs.observations import GlobalObsForRailEnv, TreeObsForRailEnv
 from flatland.envs.predictions import ShortestPathPredictorForRailEnv
 from flatland.envs.rail_env import RailEnv
 from flatland.envs.rail_generators import rail_from_grid_transition_map, sparse_rail_generator
-from flatland.envs.schedule_generators import random_schedule_generator, sparse_schedule_generator
+from flatland.envs.line_generators import sparse_line_generator
 from flatland.utils.simple_rail import make_simple_rail2
 
 
-def test_random_seeding():
+def ndom_seeding():
     # Set fixed malfunction duration for this test
-    rail, rail_map = make_simple_rail2()
+    rail, rail_map, optionals = make_simple_rail2()
 
     # Move target to unreachable position in order to not interfere with test
     for idx in range(100):
-        env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                      schedule_generator=random_schedule_generator(seed=12), number_of_agents=10)
+        env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                      line_generator=sparse_line_generator(seed=12), number_of_agents=10)
         env.reset(True, True, False, random_seed=1)
 
         env.agents[0].target = (0, 0)
@@ -44,21 +44,20 @@ def test_random_seeding():
 
 def test_seeding_and_observations():
     # Test if two different instances diverge with different observations
-    rail, rail_map = make_simple_rail2()
-
+    rail, rail_map, optionals = make_simple_rail2()
+    optionals['agents_hints']['num_agents'] = 10
     # Make two seperate envs with different observation builders
     # Global Observation
-    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                  schedule_generator=random_schedule_generator(seed=12), number_of_agents=10,
+    env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                  line_generator=sparse_line_generator(seed=12), number_of_agents=10,
                   obs_builder_object=GlobalObsForRailEnv())
     # Tree Observation
-    env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                   schedule_generator=random_schedule_generator(seed=12), number_of_agents=10,
+    env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                   line_generator=sparse_line_generator(seed=12), number_of_agents=10,
                    obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()))
 
     env.reset(False, False, False, random_seed=12)
     env2.reset(False, False, False, random_seed=12)
-
     # Check that both environments produce the same initial start positions
     assert env.agents[0].initial_position == env2.agents[0].initial_position
     assert env.agents[1].initial_position == env2.agents[1].initial_position
@@ -78,9 +77,7 @@ def test_seeding_and_observations():
             action_dict[a] = action
         env.step(action_dict)
         env2.step(action_dict)
-
     # Check that both environments end up in the same position
-
     assert env.agents[0].position == env2.agents[0].position
     assert env.agents[1].position == env2.agents[1].position
     assert env.agents[2].position == env2.agents[2].position
@@ -97,8 +94,8 @@ def test_seeding_and_observations():
 
 def test_seeding_and_malfunction():
     # Test if two different instances diverge with different observations
-    rail, rail_map = make_simple_rail2()
-
+    rail, rail_map, optionals = make_simple_rail2()
+    optionals['agents_hints']['num_agents'] = 10
     stochastic_data = {'prop_malfunction': 0.4,
                        'malfunction_rate': 2,
                        'min_duration': 10,
@@ -106,13 +103,13 @@ def test_seeding_and_malfunction():
     # Make two seperate envs with different and see if the exhibit the same malfunctions
     # Global Observation
     for tests in range(1, 100):
-        env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                      schedule_generator=random_schedule_generator(), number_of_agents=10,
+        env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                      line_generator=sparse_line_generator(), number_of_agents=10,
                       obs_builder_object=GlobalObsForRailEnv())
 
         # Tree Observation
-        env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail),
-                       schedule_generator=random_schedule_generator(), number_of_agents=10,
+        env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals),
+                       line_generator=sparse_line_generator(), number_of_agents=10,
                        obs_builder_object=GlobalObsForRailEnv())
 
         env.reset(True, False, True, random_seed=tests)
@@ -172,7 +169,7 @@ def test_reproducability_env():
                                                                             seed=215545,  # Random seed
                                                                             grid_mode=True
                                                                             ),
-                  schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=1)
+                  line_generator=sparse_line_generator(speed_ration_map), number_of_agents=1)
     env.reset(True, True, True, random_seed=10)
     excpeted_grid = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
@@ -233,7 +230,7 @@ def test_reproducability_env():
                                                                              seed=215545,  # Random seed
                                                                              grid_mode=True
                                                                              ),
-                   schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=1)
+                   line_generator=sparse_line_generator(speed_ration_map), number_of_agents=1)
     np.random.seed(10)
     for i in range(10):
         np.random.randn()
diff --git a/tests/test_speed_classes.py b/tests/test_speed_classes.py
index de8b13233a7cadd9c19331c959fc995b325de101..3cfe1b1c7f58786cf0caacde629fa3a6c704230d 100644
--- a/tests/test_speed_classes.py
+++ b/tests/test_speed_classes.py
@@ -2,8 +2,8 @@
 import numpy as np
 
 from flatland.envs.rail_env import RailEnv
-from flatland.envs.rail_generators import complex_rail_generator
-from flatland.envs.schedule_generators import speed_initialization_helper, complex_schedule_generator
+from flatland.envs.rail_generators import sparse_rail_generator
+from flatland.envs.line_generators import speed_initialization_helper, sparse_line_generator
 
 
 def test_speed_initialization_helper():
@@ -20,8 +20,7 @@ def test_rail_env_speed_intializer():
     speed_ratio_map = {1: 0.3, 2: 0.4, 3: 0.1, 5: 0.2}
 
     env = RailEnv(width=50, height=50,
-                  rail_generator=complex_rail_generator(nr_start_goal=10, nr_extra=1, min_dist=8, max_dist=99999,
-                                                        seed=1), schedule_generator=complex_schedule_generator(),
+                  rail_generator=sparse_rail_generator(), line_generator=sparse_line_generator(),
                   number_of_agents=10)
     env.reset()
     actual_speeds = list(map(lambda agent: agent.speed_data['speed'], env.agents))
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 99f731e47d488d01f281acbdc2f556b92dbf0b6d..062d56f00dd704960b316e318ee311f5c7a03539 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -9,7 +9,7 @@ from flatland.envs.agent_utils import EnvAgent, RailAgentStatus
 from flatland.envs.malfunction_generators import MalfunctionParameters, malfunction_from_params
 from flatland.envs.rail_env import RailEnvActions, RailEnv
 from flatland.envs.rail_generators import RailGenerator
-from flatland.envs.schedule_generators import ScheduleGenerator
+from flatland.envs.line_generators import LineGenerator
 from flatland.utils.rendertools import RenderTool
 from flatland.envs.persistence import RailEnvPersister
 
@@ -136,7 +136,7 @@ def run_replay_config(env: RailEnv, test_configs: List[ReplayConfig], rendering:
             _assert(a, rewards_dict[a], replay.reward, 'reward')
 
 
-def create_and_save_env(file_name: str, schedule_generator: ScheduleGenerator, rail_generator: RailGenerator):
+def create_and_save_env(file_name: str, line_generator: LineGenerator, rail_generator: RailGenerator):
     stochastic_data = MalfunctionParameters(malfunction_rate=1000,  # Rate of malfunction occurence
                                             min_duration=15,  # Minimal duration of malfunction
                                             max_duration=50  # Max duration of malfunction
@@ -145,11 +145,11 @@ def create_and_save_env(file_name: str, schedule_generator: ScheduleGenerator, r
     env = RailEnv(width=30,
                   height=30,
                   rail_generator=rail_generator,
-                  schedule_generator=schedule_generator,
+                  line_generator=line_generator,
                   number_of_agents=10,
                   malfunction_generator_and_process_data=malfunction_from_params(stochastic_data),
                   remove_agents_at_target=True)
     env.reset(True, True)
     #env.save(file_name)
     RailEnvPersister.save(env, file_name)
-    
+    return env