Kr0issant commited on
Commit
a799e55
·
1 Parent(s): 53c0e4c

add observation state

Browse files
airports/VOCB.json CHANGED
@@ -197,7 +197,14 @@
197
  "IAF_RWY_1",
198
  "FAF_RWY_1"
199
  ]
 
 
 
 
 
 
 
200
  }
201
  },
202
- "time_scale": 1.0
203
  }
 
197
  "IAF_RWY_1",
198
  "FAF_RWY_1"
199
  ]
200
+ },
201
+ "N": {
202
+ "RWY_1_REV": [
203
+ "c3a2fccd-7b0f-45ab-8e0c-f9bcf1031cee",
204
+ "IAF_RWY_1_REV",
205
+ "FAF_RWY_1_REV"
206
+ ]
207
  }
208
  },
209
+ "time_scale": 8.0
210
  }
package-lock.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "name": "atc_rl_api",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {}
6
+ }
rl_env/atc_gym.py CHANGED
@@ -3,6 +3,7 @@
3
  from openenv_core import Environment, Space
4
  import numpy as np
5
  from core.engine import SimulationEngine
 
6
 
7
  class ATCEnv(Environment):
8
  """
@@ -43,10 +44,107 @@ class ATCEnv(Environment):
43
  return obs, reward, done, truncated, {}
44
 
45
  def get_observation(self):
46
- """Get flattened observation vector"""
47
- state = self.engine.get_full_state()
48
- # Flatten and return as numpy array
49
- return np.array([0.0]) # Placeholder
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  def compute_reward(self):
52
  """Reward function: +1 for distance to runway, -100 for collision"""
 
3
  from openenv_core import Environment, Space
4
  import numpy as np
5
  from core.engine import SimulationEngine
6
+ import math
7
 
8
  class ATCEnv(Environment):
9
  """
 
44
  return obs, reward, done, truncated, {}
45
 
46
  def get_observation(self):
47
+ """Get structured observation for LLM/Agent consumption"""
48
+
49
+ segments_labels = ["North", "North-East", "East", "South-East",
50
+ "South", "South-West", "West", "North-West"]
51
+
52
+ obs = {
53
+ "aircraft": [],
54
+ "airport_status": {
55
+ "active_runways": list(self.engine.active_runways),
56
+ "runway_occupancy": {},
57
+ "wind": {
58
+ "heading": int(self.engine.wind_heading),
59
+ "speed": int(self.engine.wind_speed)
60
+ }
61
+ }
62
+ }
63
+
64
+ for rw, status in self.engine.runway_status.items():
65
+ obs["airport_status"]["runway_occupancy"][rw] = status.get("occupied_by")
66
+
67
+ aircraft_list = list(self.engine.aircrafts.values())
68
+
69
+ for i, ac in enumerate(aircraft_list):
70
+ # Compute position
71
+ distance = round(math.sqrt(ac.x**2 + ac.y**2), 2)
72
+ if ac.x == 0 and ac.y == 0:
73
+ bearing = 0.0
74
+ else:
75
+ bearing = (90 - math.degrees(math.atan2(ac.y, ac.x))) % 360
76
+ segment_idx = round(bearing / 45) % 8
77
+ segment_name = segments_labels[segment_idx]
78
+
79
+ # Compute separation
80
+ closest_callsign = None
81
+ closest_dist = float('inf')
82
+ conflict_risk = "none"
83
+
84
+ for j, other_ac in enumerate(aircraft_list):
85
+ if i != j:
86
+ dist_km = math.sqrt((ac.x - other_ac.x)**2 + (ac.y - other_ac.y)**2)
87
+ if dist_km < closest_dist:
88
+ closest_dist = dist_km
89
+ closest_callsign = other_ac.callsign
90
+ alt_diff = abs(ac.altitude - other_ac.altitude)
91
+ if dist_km < 5.0 and alt_diff < 1500:
92
+ conflict_risk = "high"
93
+ elif dist_km < 10.0 and alt_diff < 3000 and conflict_risk != "high":
94
+ conflict_risk = "medium"
95
+
96
+ if closest_dist == float('inf'):
97
+ closest_dist_val = None
98
+ else:
99
+ closest_dist_val = round(closest_dist, 2)
100
+
101
+ # Compute alerts
102
+ alerts = []
103
+ if ac.fuel_level < 10 or ac.emergency_index >= 1:
104
+ alerts.append("low_fuel")
105
+ if ac.emergency_index == 3:
106
+ alerts.append("critical_emergency")
107
+
108
+ # Intent block additions
109
+ dist_to_thresh = None
110
+ if ac.runway_threshold:
111
+ tx, ty = ac.runway_threshold["x"], ac.runway_threshold["y"]
112
+ dist_to_thresh = round(math.sqrt((tx - ac.x)**2 + (ty - ac.y)**2), 2)
113
+
114
+ next_wp = ac.active_star if ac.active_star else "None"
115
+ if hasattr(ac, "direct_to_wp") and ac.direct_to_wp:
116
+ next_wp = ac.direct_to_wp.get("name", "Direct")
117
+
118
+ ac_obj = {
119
+ "callsign": ac.callsign,
120
+ "position": {
121
+ "segment": segment_name,
122
+ "distance": distance,
123
+ "altitude": int(ac.altitude),
124
+ "target_altitude": int(ac.target_alt)
125
+ },
126
+ "motion": {
127
+ "heading": round(ac.heading, 1),
128
+ "target_heading": round(ac.target_heading, 1),
129
+ "speed": int(ac.speed),
130
+ "target_speed": int(ac.target_speed)
131
+ },
132
+ "intent": {
133
+ "state": ac.state,
134
+ "assigned_runway": ac.target_runway_id,
135
+ "distance_to_threshold": dist_to_thresh,
136
+ "next_waypoint": next_wp
137
+ },
138
+ "alerts": alerts,
139
+ "separation": {
140
+ "closest_traffic": closest_callsign,
141
+ "distance": closest_dist_val,
142
+ "conflict_risk": conflict_risk
143
+ }
144
+ }
145
+ obs["aircraft"].append(ac_obj)
146
+
147
+ return obs
148
 
149
  def compute_reward(self):
150
  """Reward function: +1 for distance to runway, -100 for collision"""
run_server.py CHANGED
@@ -32,11 +32,13 @@ def run_server():
32
  parent_dir = os.path.dirname(script_dir)
33
  ui_dir = os.path.join(script_dir, "visualizer")
34
 
 
 
 
35
  if args.only_ui:
36
  print("Starting ONLY Frontend (Vite)...")
37
  try:
38
- # Use shell=True for 'npm' on Windows
39
- subprocess.run(["npm", "run", "dev"], cwd=ui_dir, shell=True)
40
  except KeyboardInterrupt:
41
  print("\nStopping UI...")
42
  return
@@ -48,7 +50,7 @@ def run_server():
48
  if args.ui:
49
  print("Starting Frontend (Vite)...")
50
  try:
51
- ui_process = subprocess.Popen(["npm", "run", "dev"], cwd=ui_dir, shell=True)
52
  print(f"Frontend process started (PID: {ui_process.pid})")
53
  except Exception as e:
54
  print(f"Error starting frontend: {e}")
 
32
  parent_dir = os.path.dirname(script_dir)
33
  ui_dir = os.path.join(script_dir, "visualizer")
34
 
35
+ # Helper to run npm commands robustly
36
+ npm_exe = "npm.cmd" if os.name == 'nt' else "npm"
37
+
38
  if args.only_ui:
39
  print("Starting ONLY Frontend (Vite)...")
40
  try:
41
+ subprocess.run([npm_exe, "run", "dev"], cwd=ui_dir)
 
42
  except KeyboardInterrupt:
43
  print("\nStopping UI...")
44
  return
 
50
  if args.ui:
51
  print("Starting Frontend (Vite)...")
52
  try:
53
+ ui_process = subprocess.Popen([npm_exe, "run", "dev"], cwd=ui_dir)
54
  print(f"Frontend process started (PID: {ui_process.pid})")
55
  except Exception as e:
56
  print(f"Error starting frontend: {e}")
visualizer/package-lock.json CHANGED
@@ -2497,9 +2497,9 @@
2497
  }
2498
  },
2499
  "node_modules/vite": {
2500
- "version": "8.0.3",
2501
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz",
2502
- "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
2503
  "dev": true,
2504
  "license": "MIT",
2505
  "dependencies": {
@@ -2524,7 +2524,7 @@
2524
  "peerDependencies": {
2525
  "@types/node": "^20.19.0 || >=22.12.0",
2526
  "@vitejs/devtools": "^0.1.0",
2527
- "esbuild": "^0.27.0",
2528
  "jiti": ">=1.21.0",
2529
  "less": "^4.0.0",
2530
  "sass": "^1.70.0",
 
2497
  }
2498
  },
2499
  "node_modules/vite": {
2500
+ "version": "8.0.5",
2501
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.5.tgz",
2502
+ "integrity": "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==",
2503
  "dev": true,
2504
  "license": "MIT",
2505
  "dependencies": {
 
2524
  "peerDependencies": {
2525
  "@types/node": "^20.19.0 || >=22.12.0",
2526
  "@vitejs/devtools": "^0.1.0",
2527
+ "esbuild": "^0.27.0 || ^0.28.0",
2528
  "jiti": ">=1.21.0",
2529
  "less": "^4.0.0",
2530
  "sass": "^1.70.0",